Releases: 42U/laqrumcode
Release list
v0.9.0
Security-hardening release: a full-repo security & privacy audit (9 findings, all remediated — report with locked hypotheses and appended outcomes archived on the operator share), the retirement of the legacy root:root database credential, and the completion of PR #22's soul-pipeline rescue.
Security
- Full-repo audit fixes (LAQ-SEC-001…009). The web-UI launch flow no longer exposes the master hook-API token via the opener's argv, terminal output, or browser history —
/ui/authnow accepts only a single-use 60-second nonce minted over the Bearer-authed loopback API (POST /ui/mint), and master-token-in-URL is rejected outright.daemon.log/auto-drain.logopen at 0600 (with convergence chmod for existing installs; rotation keeps generations tight) since warn-level lines can quote graph content. Backup dumps write into 0700 directories with 0600 files. Both loopback HTTP surfaces reject non-loopbackHostheaders (DNS-rebinding defense-in-depth; shared policy insrc/shared/net.ts). Production npm tree audits clean (hono 4.13.2, fast-uri 3.1.5, body-parser 2.3.0, tar 7.5.22 — all transitive; one dev-only Windows-only esbuild LOW accepted under vite's pin).drop-test-namespacesis now a Node script using the admin credential file (the oldcurl -u root:rootwas dead post-hardening and leaked credentials via argv);setup.shstops suggestingcurl | sh. Audited clean with no findings: the SQL-interpolation surface, auth design, web-UI serving, download pinning, and multi-user isolation. root:rootretired (Phase 3 credential chain). External SurrealDB targets resolve credentials as: explicitSURREAL_USER/SURREAL_PASSverbatim → the managed per-user cred file → legacyroot:rootlast resort. Discovery tries the chain per candidate port and propagates the winner, closing a split-brain hazard where a hardened instance (rotated root) looked like "not a laqrumcode DB" and triggered a fresh managed spawn.scripts/provision-scoped-users.mjsdefines per-project root-level EDITOR users (full data + schema DDL, no user management) converged with the managed cred files;scripts/rotate-root-cred.mjsrotates root to a strong secret with persist-before-rotate ordering and prints rejection receipts for the old default. All maintenance scripts share the same resolution (scripts/surreal-cred.mjs); live-DB tests resolve identically and pass against hardened and unhardened instances.
Added
- Redaction covers database/service credential shapes (GH #16 extension): HTTP Basic-auth header values,
*_PASS/*_PASSWORD/*_TOKEN/*_SECRET/*_API_KEY=env-style assignments (placeholder-aware),user:pass@URL credentials, and quoted JSONpass/password/token/secretvalues — with false-positive guards pinned by tests (MAX_TOKENS=4096,$VARindirection,<placeholder>forms stay untouched). scripts/scrub-stored-secrets.mjs: stdin-fed (never argv), receipt-printing retro-scrub that replaces known secret literals acrossturn/turn_archive/memory/concept/monologuewith the standard placeholder. The audit's own scrub run found zero stored matches (positive-control verified) — prevention landed with nothing to clean.- README Security & privacy section documenting the threat model and the hardening toolchain.
Fixed
- PR #22 follow-through (soul pipeline). Merged charleshmmnd's
soul_evolveearned-values fix and closed the gaps around it: the evolve prompt embeds a partial schema (nothingrequired) with explicit replace semantics; a delta-guard merge appends instead of letting delta-shaped returns wipe stored sections; tolerant per-section coercion (bare strings / alias keys / single non-array values) with a junk guard covers both soul handlers; a landed evolution re-seeds the Tier-0 soul entries (previously graduation-only, so evolution was invisible at runtime);sections_revisedcounts only confirmed writes. - Soul pipeline hardening wave 2: stale
soul_generatezombies self-complete once a soul exists (fetch gate + actionable-count gate + gracefulsoul already existscommit outcome — previously each straggler burned a full synthesis then failed); per-section caps (20/10/20/10, mode-aware, logged) and a 50-entry revisions trim bound soul growth;reviseSoulGuardedcloses the concurrent-evolve lost-update race with a value-CAS single-shot write (probed live:+=array concat, UPDATE-on-missing no-op, exact array-of-object guard equality);seedSoulAsCoreMemoryis create-first with prefix-paired archiving (a crash can no longer leave Tier-0 without soul entries); tri-statecreateSoulkills the double-graduation-event race;SoulDocument.emotional_dimensionssaysdescription, matching schema and writers.
Known follow-ups
- The daemon-gated R6 TCP spawn integration test cannot run on a machine whose live daemon owns the socket (it verifies singleton-spawn semantics); it remains the only red test in that environment.
- One dev-only npm LOW (esbuild under vite 7.3.5, Windows dev-server) awaits a vite major.
v0.8.7
Added
- The daemon now replaces itself when its connection-level self-heal proves futile. Production incident (2026-08-02 → 08-04): every query blew the 60s deadline, every zombie-reconnect "succeeded" (fresh
Surreal, connect + signin round-tripped), and the next query died again — for two days — while an identical fresh process against the same server answered in under a second. The 0.7.118 self-heal kept rebuilding the one component that wasn't broken (the connection) and had no feedback that its medicine wasn't working; hooks failing open at 50s made the outage silent, and the valid pid file blocked every replacement spawn. AWedgeDetectorper store now tracks the streak of query-deadline timeouts and futile completed reconnects; when a streak crosses 10 timeouts and 3 futile heals and 3 minutes with zero successful queries (any success resets everything — a merely-slow server can never escalate), the store concludes the fault is process-level state a connection rebuild cannot reach and hands off to the daemon'sgracefulCleanup, whose existing 8s watchdog guarantees the exit even when the wedged store hangs the drain. The pid-file spawn guard then brings up a clean process on the next hook/MCP demand — turning this failure class from a multi-day silent outage into a ~minutes blip. Both the primary and the dedicated maintenance store escalate; standalone embedders (mcp-server.ts, tests) stay log-only since a library must notprocess.exit(). Knobs:LAQRUMCODE_WEDGE_STREAK_MS(floor, clamped 30s–1h) andLAQRUMCODE_WEDGE_EXIT_DISABLED=1(log-only escape hatch).test/wedge-escalation.test.ts(11) pins the thresholds, the reset-on-success semantics, the single-burst floor, the fire-once latch, and the shutdown/disabled suppressions.
v0.8.6
The loop guard finally works, and the release script can no longer lie about what it bumped.
Fixed
- The planning gate now fires on silence, not on volume of work. It keyed on
session.toolCallsSinceLastText— the right quantity — but nothing ever reset it: the only code that zeroed it on assistant text (engine/hooks/llm-output.ts) is imported solely by a test and is not among the hook methods the daemon registers, and Claude Code's hook surface has no assistant-text event at all. So it trackedtoolCallCountexactly and interrupted long, legitimate investigations to "summarize progress". The count is now derived from the transcript (countToolCallsSinceText) rather than tracked in memory, which also makes it correct across daemon restarts, relay reconnects and session-map eviction. Two structural facts were verified against a live transcript rather than assumed: Claude Code writes each content block as its own JSONL entry, and the entry carrying atool_useis flushed before the tool runs.thinkingblocks deliberately do not reset the count (thinking happens inside loops too), andtool_resultblocks — which arrive asuser-typed entries — do not either, since treating them as user input would reset on every call and disable the gate. Fails open: an unreadable transcript keeps the gate quiet. Measured live mid-session: old predicate 26 (fires), new predicate 2 (quiet). Closes #20. scripts/bump-version.shmissed surfaces and printed a green summary anyway.package-lock.json(both refs) andsrc/mcp-server.tswere never bumped, and its staleness check only verified surfaces it already knew about — self-fulfilling, since it cannot discover a surface nobody wired in. v0.8.5 shipped with two stale surfaces because of this. The script now bumps all seven surfaces (package-lock vianode, never a blanketsedthat would corrupt dependency pins), verifies each explicitly, and then sweeps the repo for the old version, failing on anything still carrying it — so an unknown surface breaks the run instead of passing quietly.bump-version.shno longer touches git by default. It rangit add -A && git commit && git tagunconditionally, which on v0.8.5 swept ten unrelated fixes intochore: bump to v0.8.5and violated QA-BEFORE-BUMP.--commitand--tagare now opt-in, and--commitrefuses when the working tree holds changes outside the version surfaces. Adds--skip-tests(the README badge ran the full suite on every bump) and semver validation of the argument.
Added
test/loop-guard-transcript.test.ts(16) — synthetic transcripts, no database, runs in CI. Verified fail-on-revert.test/tier1-legacy-migration.test.ts(2) — exercises the v0.8.5 legacy tier-1 migration against a live throwaway database, becausestring::starts_withthrows on a NONEsession_idandqueryExecswallows it, so a missing guard would make the migration a silent no-op that still reports success.
v0.8.5
Directive delivery and daemon stability. Two of the fixes in this release correct regressions introduced earlier in the same release cycle and caught by the pre-release QA waterfall before any tag existed — the notes below describe net behaviour against 0.8.4.
Fixed
- Tier-0 directives were silently truncated at 800 chars.
MAX_CORE_MEMORY_CHARSapplied a flat per-item guillotine with no signal to anyone. Because importance correlates with length and truncation eats the tail — where a rule's concrete specifics live — this preferentially destroyed the most important directives. On a real 25-entry install, a majority were being cut mid-clause every turn while ~4k chars of the tier-0 budget sat unused.perItemCapFor()now scales the cap with priority, andapplyCoreBudgetVerbose()fits entries in two passes: admission at the flat floor first, then leftover slack spent raising caps highest-priority-first (partial upgrades allowed). Nothing is dropped that would have fit before, nothing gets less room than before, and the budget is actually used. Measured on the same install: 25/25 entries injected, 0 dropped, full budget consumed. (#19) TIER0_MAX_TOTALcapped tier 0 by entry COUNT, which is the wrong unit — entries vary by an order of magnitude in size, so a count cap refuses writes while under budget and permits sets that overflow. Admission now tests the candidate against the same character budget the renderer enforces, and refuses with something actionable (budget_fullnames the weakest entries;would_evictnames only what the candidate would actually displace, diffed against a baseline so pre-existing overflow is reported separately rather than blamed on the new entry). Theupdatepath is checked too, not justadd. (#19)- The injection envelope was deleting its own section tags.
wrapMemoryContextranstripStructuralTagsover the fully assembled context, andrecalled_memory/active_directives/session_directives/reflection_contextare all on that list — so the labels laqrumcode had just written were stripped on the way out and tier-0 and tier-1 directives reached the model as one unlabelled run of bullets. Breakout protection moved to the content (retrieved nodes, core-memory text and category, skill and reflection fields, resurfacing memories, wakeup briefing, compaction summary, soul summary), where the wrapper tags survive;stripReminderWrappernow removes only<system-reminder>. (#21) stripStructuralTagswas single-pass and defeatable by nesting — deleting an inner tag splices the outer halves into a live one, so depth-N nesting needed N+1 passes. Both strippers now run to a fixpoint (guaranteed to converge, since every effective pass strictly shortens the string) and neutralize any residue if the safety bound is ever reached.- Tier-1 "session-pinned" directives were pinned to nothing.
getAllCoreMemoryhad no session filter and nothing retires the rows, so every session directive ever written loaded into every later session — including entries whose own text says "this session". Root cause was deeper than the read path: the MCP relay inventedmcp-client-<pid>as its session id while the hook path keyed on Claude Code's session UUID, so the two id spaces never intersected. The relay now sendsCLAUDE_CODE_SESSION_ID(resolveSessionId(), with an explicitLAQRUMCODE_SESSION_IDpin still winning forauto-drain), unifying the spaces; tier-1 reads are scoped to the asking session; and a one-shot migration retires legacymcp-client-*rows explicitly rather than letting them go quietly invisible. This also fixesinjectedSectionsinvalidation (it was clearing the cache on the session that does not build the prompt) and makes the per-session tier-0 write cap survive a relay restart. (#21) archiveOldTurnscould stop draining silently. The anti-join ran in-DB withLIMITapplied after the membership filter, so the whole backlog paid an O(stale × referenced) per-row test and crossed the 8s TIMEOUT once it grew. Rewritten as indexed scans plus a pure O(stale + referenced) Set anti-join (selectUnreferencedTurns), now paged rather than capped at a single window — referenced turns stay candidates forever and pile up at the head of the scan, so a fixed window eventually yields nothing on every run. The job also records astatus='error'row on failure so the 30-minute backoff engages instead of re-firing the full scan every boot, and warns when a non-empty candidate set yields nothing archivable.- Bootstrap maintenance no longer re-hammers the database during a daemon flap — deferred and gated on the daemon having retained an IPC client, so transient cold-boot daemons skip it.
- Daemon spawn-lock race. The holder pid is now stamped at
O_EXCLacquisition, so a racer can no longer read an empty lock (Number("") === 0), judge it dead, and spawn a second daemon concurrently. - Reconnect thundering herd — jittered backoff before an mcp-client reconnect so a single daemon exit doesn't make every relay re-handshake in the same instant.
Changed
- The tool-call planning gate keys on
toolCallsSinceLastTextrather than total tool calls — the signal that separates a loop from productive work. Documented as inert: nothing in the production hook path produces that signal today, so it fires exactly where the old predicate did. See #20. - The
softInterruptblock message no longer claims the user pressed Ctrl+C; nothing sets that flag from a real interrupt.
Known follow-ups
- #20 — wire an assistant-text producer so the loop guard can actually distinguish looping from work.
- Age-based archival of orphaned tier-1 rows (hygiene; read-scoping is the correctness mechanism).
retrieval_outcomeid prefetch inarchiveOldTurnsis unbounded and re-Set per page.- Integration tests gated on a live SurrealDB return early rather than skipping, so they report green in CI, which provides no database.
v0.8.4
The dense+sparse hybrid retrieval arm — and the correction of an earlier wrong "infeasible" call.
Added
- Hybrid dense+sparse retrieval (BM25 full-text + 3-way RRF). A lexical/sparse arm now joins the dense vector arm and the keyword/tag arm in the injection pipeline.
schema.surqldefines afts_analyzer(blank/class/camel/punct tokenizers + snowball English stemming) and FULLTEXT BM25 indexes onconcept.content,turn.text,memory.text,artifact.description,skill.description.SurrealStore.fulltextSearch()runs a per-term@n@OR query (recall) ranked by summedsearch::score, unioned into the candidate pool and folded into the graph-seed Reciprocal Rank Fusion. This adds recall of exact-term / rare-token / code-identifier rows the dense embedding misses entirely. (BGE-M3's sparse heads aren't reachable via node-llama-cpp, so the sparse arm is SurrealDB-native BM25 — no model sparse head needed.) - Offline retrieval benchmark (
scripts/retrieval-benchmark.mjs) — NDCG/MRR/Recall over the labeledretrieval_outcometable, to gate ranker changes with a number instead of a guess. scripts/drop-test-namespaces.sh— durable cleanup for the vitest temp namespaces (kctest_*) that accumulate on the local SurrealDB instance.
Changed
- Managed SurrealDB binary pinned 3.0.5 → 3.1.4 (
bin-manifest.json) — aligns fresh installs with the version everything is tested against. Deliberately not 3.1.5: issue #7383 is a 3.1.5-specific RocksDB OOM under full-text load that would regress the new hybrid. All 5 per-platform sha256 from the GitHub release digests; linux-x64 download-verified.
Notes
- SurrealDB 3.x full-text BM25 uses
FULLTEXT ANALYZER(renamed from 2.xSEARCH ANALYZERin 3.0.0-beta).search::scorereturns 0 on a near-empty index (#7290 fresh-DB corpus-stats edge) but is correct at real scale —fulltextSearch()falls back to a term-frequency rank for the empty-index case.skillindexesdescription, notbody(full skill bodies are large enough to stall the FULLTEXT build / schema-apply).
v0.8.3
A SOTA-retrieval optimization pass: deliberate recall() and passive injection now share the full ranking stack, the injected set is diversity- and hybrid-fusion-aware, the learned scorer trains on the right signal with hard-negative emphasis, and heavy maintenance can no longer stall hook serving.
Performance
recall()routed through the real ranking stack. The deliberaterecall()tool ranked by raw cosine only — bypassing the semantic dedup + bge-reranker-v2-m3 cross-encoder that passive auto-injection uses, so explicit recalls were ranked worse than what an agent passively receives. It now runs dedup → cross-encoder rerank (0.6·cosine + 0.4·cross). Also fixes a default-limit mismatch (was 3; schema documents 5).- MMR diversification of the injected set. A Maximal Marginal Relevance pass (λ=0.7) between the cross-encoder rerank and token-budget selection — greedy argmax(λ·score − (1−λ)·maxCosineToPicked) over the selection-eligible items — so a redundant concept-family can't crowd out coverage.
- Hybrid dense+keyword fusion (RRF). The dense (vector) and keyword (tag) candidate rankings were merged by plain id-concat. They're now fused with Reciprocal Rank Fusion (k=60) for graph-walk seed selection, so a keyword-strong concept with mediocre cosine can still seed expansion.
- Hard-negative weighting in the ACAN training loss. The learned scorer's pointwise objective now upweights hard negatives (high retrieval cosine, low utilization) 2.5×. Weights version bumped 2→3 → clean cold-start retrain (ACAN falls back to WMR during the transition).
- HNSW M=16 / EFC=200. Bumped the vector indexes from SurrealDB's M=12/EFC=150 defaults to the SOTA-minimum (recall is monotonic in M; extra RAM offset by F32). New installs get it automatically; existing installs pick it up in the
scripts/migrate-hnsw-f32.mjsrebuild (now F32 + M/EFC together).
Fixed
- daemon: Heavy maintenance jobs now run on a dedicated SurrealDB connection, so a maintenance deadline → zombie → reconnect can no longer reject in-flight hook queries (the architectural complement to 0.8.2's per-query
TIMEOUT). Best-effort with a safe fallback to the shared store — cannot regress maintenance. - acan: The learned scorer trained on a recomputed aux-feature vector that didn't match the one used at scoring time (train/inference skew). It now persists and trains on the exact stage-time
aux_features.
Known follow-ups (benchmark-/ops-gated)
- True dense+sparse hybrid (BGE-M3 sparse/ColBERT heads) is blocked:
node-llama-cppexposes only dense embeddings, and SurrealDB BM25 full-text parse-errored on this build. A BM25 sparse arm + RRF-into-final-scoring needs a recall benchmark. - Full listwise RankNet ACAN loss needs offline NDCG/MRR validation before activation.
- Aggressive HNSW (M 24–48 / EFC 300) and a Qwen3-Embedding swap (model path + dims are already config-swappable via
EMBED_MODEL_PATH) are a benchmark + re-embed decision.
v0.8.2
Fixed
- daemon:
archiveOldTurnsnow caps its scan with a server-sideTIMEOUT 8s. On an overloaded SurrealDB instance the maintenance query could otherwise blow the 60s client deadline → flag the shared connection "zombie" → tear down the socket that also serves hook memory-injection, the cause of intermittent "daemon unreachable". A SurrealDBTIMEOUTerror isn't"deadline exceeded", so it also avoids the retry-doubling.
Performance
- HNSW indexes: all 9
*_vec_idxvector indexes now pinTYPE F32instead of SurrealDB's F64 default — lossless (embeddings are F32-precision) and ~halves in-RAM index size. New installs get it automatically; existing installs rebuild viascripts/migrate-hnsw-f32.mjs.
v0.8.1
Fixed
- auto-drain: terminate the headless-extractor spawn args with
--soDRAIN_PROMPTis no longer swallowed by the variadic--disallowed-tools(claude 2.1.x). The bug made every drain exit 1 in ~6s with no queue progress — flipping theautoDrainmaintenance job RED and leavingpending_workpermanently undrained. Verified against claude 2.1.195 (live drain 6→4).
Changed
scripts/migrate-legacy-graph.mjs: scaled the legacy-graph migrator to 300k+ rows — keyset-paginated streaming reads (flat memory), bulkINSERT RELATION INTOfor edge tables (~100x faster than per-recordRELATE), and a resilient per-record skip+log fallback for rows the destination schema rejects. Preserves the content-safe verbatim-copy contract.
v0.8.0
🔄 KongCode is now LaqrumCode
Same engine, new name — this is the rebranded marketplace ship. Because Claude Code identifies plugins by plugin@marketplace (no rename/alias path), existing installs need a one-time manual migration:
/plugin uninstall kongcode@kongcode-marketplace
/plugin marketplace remove kongcode-marketplace
/plugin marketplace add 42U/laqrumcode
/plugin install laqrumcode@laqrumcode-marketplace
Then restart Claude Code. MCP tools are now mcp__plugin_laqrumcode_laqrumcode__* — update any permission allowlists that referenced the old mcp__plugin_kongcode_* ids.
Moving an existing memory graph from ~/.kongcode? This release ships a content-safe migrator (copies your graph verbatim into the laqrum namespace; never rewrites your memory text):
LEGACY_BRAND=kong node scripts/migrate-legacy-graph.mjsOld
42U/kongcodeURLs 301-redirect here, so links and stars carried over.
Changed
- Rebranded to laqrumcode — plugin id, marketplace name, MCP tool prefix (
mcp__plugin_laqrumcode_laqrumcode__*), runtime paths (~/.laqrumcode), and SurrealDB namespace (laqrum). Existing installs must reinstall under the new id (see release notes).
Added
scripts/migrate-legacy-graph.mjs— content-safe migrator to import a pre-rebrand memory graph into thelaqrumnamespace (verbatim content copy + surgical structural-id fixups;LEGACY_BRAND/NEW_BRANDparameterized).
Hardened — enterprise 1M-install readiness pass (branch harden-1m-and-rename)
A 118-agent review (fan-out → triage → 3-lens adversarial verify) of the whole
engine for ~1M independent single-host installs surfaced 36 confirmed defects
(+ K0); 15 were refuted (one — orchestrator_metrics retention — because the
proposed hard-DELETE would have violated the Tier-0 NEVER-DELETE directive). All
confirmed defects are fixed below; full suite green (1318 tests). Frame: 1M users
= 1M local daemons, so the bar is deterministic correctness + per-host
resource/cost discipline, not server sharding. No content-table hard-deletes were
added; all new indexes are non-UNIQUE / IF NOT EXISTS (boot-safe).
- K0 — drain pipeline unbroken.
pending_work.statusSCHEMAFULL enum was
missing thecommittingtransient that batch-2a's C1 CAS writes, so
commit_work_resultsfailed deterministically and NO work item could commit.
Enum fixed; addedtest/schema-status-enum-drift.test.ts(non-mocked static
guard for the whole code-vs-schema status-enum class — the mocked
commit-claim-guardtest couldn't see it). - Concurrency (CRITICAL/HIGH): K3 concept dedup race sealed via deterministic
concept:⟨sha256(lowercased content)⟩record id (no risky UNIQUE migration);
K15 stale-recovery no longer reverts an in-flightcommittingrow (clock reset
at the CAS + pre-write ownership re-assert); K41 commit CAS made idempotent
across a withRetry re-fire viacommitting_token; K31 causal_graduate claims
chains at fetch time (RETURN BEFORE) so concurrent drains can't double-synthesize
skills; K10 auto-drain lock no longer steals a live child's lock by age / unlinks
a sibling's lock; K21 utility-cache running-average replaced with commutative
util_sum/countaccumulators; K9 consolidate mutual-archive guarded; K42
createSoul idempotent. - Scalability hot path (CRITICAL/HIGH): K4
tagBoostedConcepts, K19/K20
consolidate + concept-dedup, K18 reflection membership, K14 PreCompact reads —
all converted off full linear cosine/WITH NOINDEXscans to HNSW-KNN /
index-served bounded reads; K2/K23 retrieval-eval bounded + batched + moved off
the Stop critical path; K6 deadline now cancels the inner pipeline (AbortSignal
honored); K13 cross-encoderrankAllgot a timeout + circuit breaker; K28
graduation aggregations no longer run twice per prompt; K8/K29/K33 added missing
created_at/timestamp/importanceindexes + turn_score retention. - Resilience / resource (HIGH/MED/LOW): K1 dead session-reaper wired +
hard sessions-Map cap; K48_observedFilePathscap + bounded scan; K11
daemon close() drains in-flight RPCs before disposing the store; K12 RPC + embed
backpressure; K5 ingest embed-failure degrades to an un-embedded (heal-able) row
instead of dropping the turn; K16 concept backfill keys offcontent(not the
deadnamecolumn); K17 embedding_cache periodic prune + pruned_at reset; K51
memory backfill embeds the original short target; K32/K38/K39 added connect /
readiness / download timeouts; K34 ACAN training uses transferable buffers; K40
rerank all-dropped floor; K47 drain-log fd closed on spawn failure; K50 relay
process-level rejection handlers; K35 UI search bounded.
Hardened — round 2/3 (loop-until-dry): regressions in the round-1 fixes + missed defects
A second adversarial review (regression audit of the fix commit + missed-issues
sweep) found 19 more confirmed defects — proof that one pass wasn't enough.
Most were regressions the round-1 fixes themselves introduced; two were genuinely
missed; three filings were refuted by running the real SurrealDB binary. All 19
fixed; full suite green (1407 tests).
- Missed CRITICAL — Windows was entirely non-functional (R6): the MCP client
spoke only Unix sockets (existsSync gate) while the daemon binds TCP on win32,
so memory was dead on 100% of Windows installs andLAQRUMCODE_DAEMON_TRANSPORT=tcp
was a no-op. Client is now transport-aware (TCP on win32 / env opt-in, fixed port
18764), with a real meta.handshake readiness probe; dup-daemon protection preserved. - Missed HIGH — ReDoS on the shared event loop (R4/R16): the path-extraction
regexes ([\w./~-]+\.ext) had catastrophic backtracking (~6s stall per
PostToolUse/PreCompact on dot-heavy tool output). Replaced with one shared
tokenizing extractor; K48's length cap alone didn't bound backtracking. - Regressions in round-1 fixes: R1 K21's deterministic-id
mucUPSERT collided
with the retainedmuc_mid_idx UNIQUEon legacy rows (froze utility writeback on
upgrade) → removed the now-redundant UNIQUE + migration folds legacy rows; R2 K13's
rerank breaker used a submit-time clock (tripped on queue depth) → single serial
FIFO with dequeue-time clock; R5/R14/R15 K39's download used a wall-clock abort that
killed healthy slow downloads + leaked/hung on write errors →stream.pipeline()
rewrite (connect-phase timeout only); R7 K1's cap evicted FIFO not LRU → recency
bump; R8 K31 stranded the graduation backlog on a failed synthesis → bounded claim +
un-stamp recovery; R9 K15's ownership re-assert was TOCTOU →markTerminalgates the
terminal stamp oncommitting_tokenat write time; R10 K6 left staging/access-bump
unguarded on abort; R11 K3's hash id broke on all-digit prefixes → constant letter
prefix; R12 K16 healed concepts to a searchTerms-stripped vector → concept
embedding_target; R3 K2's detached eval wasn't drained on shutdown → daemon
pending-task registry awaited in gracefulCleanup. - Coverage gaps (R19/R20/R22/R23): added the real-behavior tests whose absence let
the round-1 fixes ship under-verified (the mocked-test-blindspot class that hid K0).
Hardened — round 4/5 (convergence): regressions in the round-3 fixes + 1 pre-existing
A third review round (regression audit of the round-2/3 commit + whole-campaign
completeness critic) found 6 more (trend: 36 → 19 → 6, converging); all fixed,
full suite green (1451 tests).
- S1 (pre-existing, HIGH): a failed schema apply left
isAvailable()returning
true (it checked only socket connectivity), so the daemon served writes for its
whole lifetime WITHOUT thepending_workUNIQUE seal the dedup/committing_token
CAS relies on. Added aschemaAppliedgate (isAvailable = isConnected && schemaApplied), a bounded schema-apply retry, and a reconnect-path re-arm so a
degraded store self-heals. - S6 (Windows multi-user isolation): R6's TCP path bound a flat shared loopback
port, so a 2nd OS user on a Windows host adopted the 1st user's daemon + graph.
Port is now derived per-user (username/SID hash offset, symmetric client/daemon),
plus a 0600 per-user handshake token (loopback TCP isn't user-isolated like the
Unix socket). - Regressions in the round-3 fixes: S3 — R4/R16's replacement left a quadratic
suffix-strip regex and pre-compact lost its size cap → non-backtracking reverse
scan (0.1ms vs 4.2s @64kb) + restored 64KB cap; S7 — R4/R16 silently dropped
Windows backslash paths → backslash added to the token splitter; S4 — R9's guarded
terminal stamp mis-reported a retry-idempotent success asskipped→ self-token
confirmation SELECT + stripped the contradictoryskippedfrom the success
envelope (a pre-existing ambiguity); S5 — R8's graduation un-stamp missed the
stale-recovery path → stale-recovery now un-stampswon_chain_ids.
Hardened — round 6/7 (convergence): regressions in the round-5 fixes
A fourth review round found 4 more (trend: 36 → 19 → 6 → 4), all regressions
in the two most complex round-5 fixes (S1 schema-gate, S6 Windows transport),
fixed by hand; full suite green (1451 tests).
- T1: S1's
schemaAppliedgate could latch a healthy daemon to
isAvailable()===falsepermanently (a transient schema-reapply timeout behind
the reconnect early-return) — worse than the bug it fixed. MadeschemaApplied
monotonic (the schema persists in the DB and is idempotent; reconnect only
re-applies when never-applied) and only clears the zombie flag when usable. - T2: S6 enforced the handshake token on every connection but the co-located
UDS client never sent it → self-lockout whenLAQRUMCODE_DAEMON_PORTis set on
Linux/macOS. Client now attaches its own 0600 token whenever readable. - T3: S6's per-user port window [18764, 28763] overlapped the managed-SurrealDB
port 18765 → ~1/10000 use...
v0.7.130
Added — update_skill MCP tool
- New
update_skilltool revises an EXISTING DB-resident skill — the counterpart
tocreate_skill, which deliberately rejects name collisions. Patches any of
body/description/steps/preconditions/postconditionson the
skill matched byname, and re-embeds sorecall(scope="skills")reflects
the new content. Before this, the only way to revise a shipped skill body was
raw SurrealQL — and a naiveUPDATE skill SET body = ...left the OLD embedding
in place (the maintenance backfill only fills rowsWHERE embedding IS NONE, so
it never refreshes a stale-but-present vector), silently desyncing the vector
index from the body. If the embedding service is unavailable the tool sets
embedding = NONErather than leaving it stale, so the backfill recomputes it. - Wired across all 5 MCP tool surfaces;
test/update-skill.test.tscovers the
re-embed, NONE-fallback, not-found, no-field, and short-body paths.
Fixed — ship the new tool's compiled output
- v0.7.127–0.7.129 failed CI for one root cause: the release commits staged
sources withgit add -u, which skips NEW untracked files — so the compiled
dist/tools/update-skill.jswas never committed. CI runs the test suite
against the committeddist/(there is no prebuild step), sonode dist/mcp-server.jshit a missing-module import, crashed on startup, and the
mcp-handshaketest timed out waiting for a response. The earlier "CI timing"
and "contention" theories were wrong — re-running v0.7.126's workflow stayed
green, isolating the cause to this release's diff. The dist file is now
committed; release staging usesgit add -Aso generated output can't be
dropped again. (update-skill.test.tsis a mock-based unit test, kept from the
investigation — fast, no DB dependency.)