Commit Graph
652 Commits
Author SHA1 Message Date
KolisCode 48fa9cbc03 fix(cypher): return whole value for composite and escaped properties
json_extract_prop() scanned a non-string property up to the first ',' and a
string property up to the first '"', ignoring nesting and backslash escapes.
Any array/object property was therefore truncated at its first INTERNAL comma,
and any string containing an escaped quote was cut short.

    decorators: ["@Roles('OWNER', 'ADMIN')","@Get()"]
    projected as: ["@Roles('OWNER'

This makes decorator/route/authz queries unusable on frameworks whose
decorators take multiple arguments (NestJS, Angular, Spring). Values are now
copied as balanced constructs, honoring string state and escape pairs; scalar
and plain-string paths are unchanged.

Signed-off-by: KolisCode <jhohantma@gmail.com>
2026-07-14 22:31:44 -05:00
Martin Vogel feadbe1955 Merge pull request #467 from isc-tdyar/pr/objectscript-language-support
feat(objectscript): InterSystems IRIS ObjectScript language support
2026-07-15 01:33:19 +02:00
Martin Vogel aa2366811b fix: eliminate installer filesystem races
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-14 21:13:52 +02:00
Martin Vogel 5ae6ef7875 fix: migrate Windows Claude hook scripts safely
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-14 19:08:54 +02:00
Martin Vogel c22020d18d test: cover Windows hook migration safety
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-14 18:50:35 +02:00
Martin Vogel 90fab0844a test: model Windows agent hook contracts
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 05:21:55 +02:00
Martin Vogel be1e98fd99 test: write Windows fixtures byte-exactly
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 04:17:58 +02:00
Martin Vogel eb203ff6fa test: guard POSIX filesystem checks on Windows
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 03:31:31 +02:00
Martin Vogel 62d191b6d1 fix: restore cross-platform lint and builds
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 02:46:04 +02:00
Martin Vogel a537d3bd84 fix(cli): make Windows Claude hooks shell portable
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 02:18:10 +02:00
Martin Vogel 2040488cf4 fix(cli): preserve hook compatibility after rebase
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 02:00:44 +02:00
Martin Vogel d6c9d28bf5 style: apply current C formatting
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 02:00:32 +02:00
Martin Vogel f0db224140 feat: add coverage-aware agent integrations
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 01:50:03 +02:00
Martin Vogel a3903caa0b feat(cli): expand agent integration coverage
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 01:50:02 +02:00
Martin Vogel 37880b62e8 fix(store): reject null search handles
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 01:48:29 +02:00
Martin Vogel 2469ecc3a7 Merge pull request #1059 from DeusData/fix/952-laravel-facade-routes
fix(routes): mint facade-style Laravel routes and compose group prefixes
2026-07-12 22:44:38 +02:00
Martin Vogel 8f3ee086a7 Merge pull request #1058 from DeusData/fix/996-writer-wide-fopen
fix(writer): open the graph DB wide-safe for non-ASCII cache paths
2026-07-12 21:49:34 +02:00
Martin Vogel da4c00671f Merge pull request #1057 from DeusData/fix/929-windows-cmd-hooks
fix(install): Windows hook scripts get .cmd form; legacy files removed
2026-07-12 21:30:21 +02:00
Martin Vogel 277662416b test: drop accidental whole-file reformat from the probe suite
The previous commit's clang-format -i rewrote all of
test_edge_types_probe.c (+732/-358 for a 48-line test addition) - my
local formatter's whole-file drift, not real non-conformance (lint-ci
passes on the restored file). Restore the file to main's formatting
with only the two #952 tests added.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 21:18:11 +02:00
Martin Vogel 4ecfe4cd67 fix(routes): mint facade-style Laravel routes and compose group prefixes
Real Laravel apps register routes through the Illuminate facade, whose
class lives in vendor/ and is never indexed - and for that style the
graph minted ZERO Route nodes, flat or grouped. Three stacked defects:

1. The callee of Route::get(...) (scoped_call_expression) was
   extracted as bare 'get' via generic field resolution, so the
   empty-resolution route fallback ('::get' suffix table) could never
   engage. The extractor now emits the qualified 'Route::get' form,
   gated to the literal Route scope AND a route-method table match so
   Cache::get / Config::get keep bare names and can never mint junk
   (guarded by an inverse test; the path gate additionally requires a
   leading '/').
2. The sequential pass had no ROUTE_REG empty-resolution fallback at
   all (its comment claimed parity with the parallel path, which has
   one) - unresolvable route registrations were dropped entirely.
   Added, mirroring the parallel callee_suffix fallback.
3. Nothing composed enclosing prefix()->group() chains (same class as
   Spring's #734). At extraction - the only place the AST still exists
   - each route call inside group closures now walks its ancestors:
   every enclosing closure that is the argument of ->group(...)
   contributes the prefix('...') found on that group call's receiver
   chain, outer-to-inner for nested groups; middleware/name/domain
   chain links are skipped.

The existing handles_laravel_php test kept passing throughout because
its fixture defines an in-tree Route class, resolving by QN - which is
exactly how this class stayed unseen.

Closes #952

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 21:12:49 +02:00
Martin Vogel 5c9c554a2b Merge pull request #1055 from DeusData/fix/858-hook-deadline
fix(hook): raise + env-configure the augment deadline, breadcrumb on fire
2026-07-12 20:52:21 +02:00
Martin Vogel c1db9c5d02 Merge pull request #1056 from DeusData/fix/1032-aider-cli-instructions
fix(install): give Aider CLI-form instructions instead of MCP tool calls
2026-07-12 20:45:38 +02:00
Martin Vogel 71cd27f720 Merge pull request #1054 from DeusData/fix/1025-project-suffix-resolution
fix(mcp): resolve project arg by unique name tail when exact lookup misses
2026-07-12 20:14:03 +02:00
Martin Vogel d9adfeb688 fix(writer): open the graph DB wide-safe for non-ASCII cache paths
cbm_writer_open used a raw ANSI-CP fopen for the hand-rolled SQLite
writer - the one file-creating call on the dump chain without a
UTF-8-to-wide conversion. On a non-ASCII Windows profile
(C:\Users\Kovacs Janos) extract and resolve succeeded but the dump
failed with pipeline.err phase=dump: fopen could not create graph.db
under the profile-derived cache dir while cbm_mkdir_p (already wide)
had happily created the directory around it. Routed through cbm_fopen,
matching #700 (pass readers) and #973 (canonicalization); POSIX is
byte-transparent.

New Windows guard test_non_ascii_cache_dump.py indexes an ASCII repo
into a non-ASCII CBM_CACHE_DIR and requires a query_graph readback
with Function count > 0 - proving the DB was written to and reopened
from the non-ASCII cache, not merely that no error surfaced. The
existing non-ASCII guard only varied the repo path against an ASCII
cache, which is why this class went unseen.

The USERPROFILE-sourced env encoding question (Layer 2 - _environ is
ANSI-CP, not UTF-8) is deliberately not bundled; it needs verification
on a real Windows box and will be filed separately if the guard alone
does not settle it.

Closes #996

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 19:57:47 +02:00
Martin Vogel b1502bf3eb fix(install): Windows hook scripts get .cmd form; legacy files removed
Extensionless bash shims under %USERPROFILE%\.claude\hooks triggered
the Windows 'How do you want to open this file?' dialog whenever an
editor (Cursor) scanned the hooks dir, and could not execute without
bash anyway. All three Claude hook scripts (discovery gate, session
reminder, subagent reminder) now install as .cmd files with cmd syntax
on Windows - @echo off bodies, caret-escaped pipes in the reminder
text, 2>/dev/null redirects - and the install removes the pre-existing
extensionless twin so upgrades stop triggering the dialog. The
settings.json command paths follow automatically since they are built
from the same script-name macros. POSIX behavior is byte-identical and
guarded (no .cmd twin).

Closes #929

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 19:45:29 +02:00
Martin Vogel 783892aa63 fix(install): give Aider CLI-form instructions instead of MCP tool calls
Aider has no MCP support - it reads CONVENTIONS.md but can only run
shell commands. The installer wrote the shared MCP-tool-centric
instructions there, telling the model to call search_graph(...) etc.,
tools Aider cannot invoke; a reporter reasonably asked how that was
ever supposed to work (#1032).

Aider now gets a dedicated variant: the same discovery priority
expressed as runnable codebase-memory-mcp cli commands (usable via
/run), including first-index and list_projects bootstrapping, with the
no-MCP constraint stated explicitly.

Closes #1032

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 19:18:20 +02:00
Martin Vogel 39ffb97ceb fix(hook): raise + env-configure the augment deadline, breadcrumb on fire
The 300ms in-process SIGALRM budget in hook-augment self-terminated
with a silent _exit(0) on real cold starts (SQLite/mmap open under
load), so PreToolUse augmentation never appeared in real sessions
(reporter: 0/24 observed) while warm manual invocations worked - and a
fired deadline was indistinguishable from a legitimate no-match run.

Default budget is now 2000ms (the settings.json hook timeout stays the
outer backstop), overridable via CBM_HOOK_DEADLINE_MS (clamped
50..10000). When the deadline fires, the handler write()s a
pre-formatted breadcrumb (deadline, pid, the env knob to raise) to
~/.cache/codebase-memory-mcp/logs/hook-augment-timeouts.log before
exiting - fd and message are prepared at arm time so the handler stays
async-signal-safe; CBM_HOOK_TIMEOUT_LOG overrides the path for tests.
Windows keeps relying on the settings.json timeout (unchanged no-op).

Deterministic reproduction: stdin is a pipe whose writer never sends,
so the read blocks past a 60ms deadline - the timer must fire, exit 0
(fail-open), and leave the breadcrumb. RED before (no breadcrumb),
GREEN after.

Closes #858

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 19:06:47 +02:00
Martin Vogel 2432cc8bbb fix(mcp): resolve project arg by unique name tail when exact lookup misses
Agents naturally pass the repo folder name (codebase-memory-mcp), but
indexed project names derive from the full path (E:\project\graph\x
-> E-project-graph-x), so every graph tool answered 'project not found
or not indexed' while list_projects clearly showed the project.

get_project_arg now falls back to a cache-dir filename scan when no
<project>.db exists: a passed name matching exactly ONE indexed
project as a segment-aligned tail (-<name>.db) adopts that project's
full name (logged); zero or several matches keep the original name so
the existing not-found error with the candidate list fires. Exact
names never enter the scan, and internal-name drift stays #704's
fallback in resolve_store.

Closes #1025

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 18:48:34 +02:00
Martin Vogel b70812e22f test(repro): RED reproduction for #964 header QN collision
cbm_pipeline_fqn_compute strips the file extension, so a C/C++ header
and its same-stem source collide on both the module QN and the File
QN. Node upserts match by QN, so the header's File node is merged into
the source's - the header has no node of its own and can never receive
an inbound IMPORTS edge; the reporter's zero-inbound query flags 194
of 214 files.

The module-QN unification is (at least partly) load-bearing for C/C++
declaration/definition call joins, and the File-node creation sites
overlap PR #995's pending fix for #994, so the fix is deferred to land
on top of that. This RED reproduction (non-gating repro tier) keeps
the bug tracked until then. Refs #964

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 17:39:03 +02:00
Martin Vogel db330bf3a0 Merge pull request #1050 from DeusData/fix/961-ifdef-split-defs
fix(extract): recover C/C++ defs lost to #ifdef-split brace ERROR regions
2026-07-12 16:41:22 +02:00
Martin Vogel be7bece5f0 Merge pull request #1047 from DeusData/test/876-recursion-delegation-guards
test(extract): guard delegation-receiver recursion false positives
2026-07-12 16:04:43 +02:00
Martin Vogel 65a176e4d8 fix(extract): recover C/C++ defs lost to #ifdef-split brace ERROR regions
A function whose body braces are split across #ifdef/#else branches
(one open brace per branch, a single shared close) parses with an
ERROR region on the raw source - both branches are present at once, so
the braces are unbalanced - and the defs walk silently dropped it
while its callers stayed as unresolved names (cbm_path_within_root,
handle_process_kill).

The preprocessed second pass (simplecpp) already exists for macro-
hidden CALLS; it evaluates the conditionals, so one branch is chosen
and the expanded tree parses clean, with same-file token lines aligned
to the original. Recover definitions from it, adopting only defs that
(a) intersect a raw-tree ERROR region, (b) have their name visible on
the corresponding original source line (rejects header-inlined defs
whose physical expanded lines alias unrelated raw lines when
compile_commands include paths resolve), and (c) are absent by
qualified name from the raw pass.

Verified on the real instances: handle_process_kill (580-647) and
cbm_path_within_root (5070-5085) both index with correct line ranges.

Closes #961

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 15:09:07 +02:00
Martin Vogel 94be485244 Merge pull request #1045 from DeusData/fix/937-watcher-dirty-signature
fix(watcher): reindex dirty repos once per distinct state, not per poll
2026-07-12 15:09:07 +02:00
Martin Vogel 2f3be252a3 test(extract): guard delegation-receiver recursion false positives
The #599 receiver-aware narrowing (87091ed) shipped without coverage
for the delegation/store bucket reported in #876: same-named calls
whose receiver is a call result (_get_store().get inside get), a
module singleton (_default.check inside check), or a loop-local cursor
(cur.execute inside execute). All three are clean on current main;
this guard pins the bucket so it cannot regress. Refs #876

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 13:55:45 +02:00
Martin Vogel 66f2413cd9 test(contract): free node fields in calls_edge_targets helper
cbm_store_find_node_by_id heap-allocates the node's string fields;
the #871 helper left them unfreed, tripping LSan on the Linux legs.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 13:40:33 +02:00
Martin Vogel 9789a7c55e fix(watcher): reindex dirty repos once per distinct state, not per poll
A persistently dirty or untracked worktree re-triggered a full reindex
(and its DB/artifact rewrite) on every poll cycle - git_is_dirty()
answered 'is the tree dirty' where the watcher needs 'did the tree
CHANGE'. Idle repos with one uncommitted file produced sustained disk
write amplification (reported at 1 TB/day across several repos).

The watcher now tracks a dirty-state signature: FNV-1a over the
entries of git status --porcelain -uall -z plus each listed path's
(size, mtime_ns). Same signature -> no reindex; editing a dirty file,
adding/removing one, or reverting the tree to clean each produce a new
signature and trigger exactly one reindex. Submodule status is folded
in on POSIX, mirroring the old submodule dirty probe. 0 is reserved
for a clean tree; a dirty-at-baseline tree still reindexes once (the
watcher cannot know whether that state reached the DB, e.g. after a
server restart with a stale artifact).

Baselines (HEAD and signature) are now committed only after a
SUCCESSFUL reindex: check_changes stages observations in pending_*
fields, and busy-skips (index_fn > 0, e.g. pipeline lock held) or
failed runs leave the baselines untouched so the change is retried on
the next poll instead of being recorded as seen and silently lost -
the old code stored the new HEAD at check time, dropping any commit
that arrived while the pipeline was busy.

Closes #937

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 13:34:42 +02:00
Martin Vogel 8a1b5f64b8 fix(extract): stop CommonJS require() bindings from shadowing call resolution
const foo = require('./foo') emitted a Variable definition for the
binding, so calls to foo() resolved to that same-module Variable (an
import alias, not a definition) instead of following the recorded
import to the exported function - which stayed at zero inbound CALLS
and surfaced as a disconnected Variable node. ESM import bindings never
materialize as Variables; the identifier-bound require form now behaves
identically: extract_js_vars skips the Variable def exactly when the
declarator value is a require() call with a string-literal path (the
form extract_imports records with local_name = the identifier), letting
resolution fall through to the import map. Destructured requires keep
their Variable defs since the import row only records the module.

Closes #871

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 11:56:23 +02:00
Thomas Dyar 4d6f2dd34a feat(objectscript): add InterSystems IRIS ObjectScript language support
Add ObjectScript (InterSystems IRIS / Caché) as a supported language,
covering the UDL class format (.cls), MAC/INT routines (.mac/.int/.rtn),
include/macro files (.inc), and IRIS Studio Export XML.

Definition extraction (extract_defs.c): Class, Method, ClassMethod,
Property, Parameter, Index, Trigger (with body text), XData, Storage,
and Query members as graph nodes; base classes from the Extends clause.

Call dispatch resolution (extract_calls.c) — four ObjectScript patterns
that are structurally invisible to text search:
  1. ##class(Pkg.Class).Method()    explicit cross-class call
  2. ..Method()                     relative-dot self-call (the dominant
                                    intra-class form; large impact on
                                    CALLS completeness)
  3. $$$Macro                       macro expansion via a per-project
                                    table built from .inc files
  4. type inference from %New/%OpenId + declared return types

Ensemble production topology (pass_ensemble_routing.c): EnsembleItem
nodes per production component and ROUTES_TO edges resolved from
ProductionDefinition XData, plus WorkMgr .Queue("##class(X).method")
dispatch — all parsed statically at index time, no live IRIS required.

Language detection (language.c): .mac/.int/.rtn map to ObjectScript
routine directly; .cls (shared with Apex) and .inc (shared with BitBake)
are disambiguated by content, defaulting to the existing language on any
doubt so neither Apex nor BitBake detection regresses.

The two new per-project tables (macros, return types) are threaded
through a new internal cbm_extract_file_ex() so the public
cbm_extract_file() signature is unchanged.

The tree-sitter grammars for ObjectScript UDL and routine are vendored
in internal/cbm/vendored/grammars/objectscript_{udl,routine}/ from
https://github.com/intersystems/tree-sitter-objectscript (MIT, ABI 15).

Refs #462

Signed-off-by: Thomas Dyar <tdyar@intersystems.com>
2026-07-11 19:09:43 -04:00
Martin Vogel 73a3c347d9 fix(cypher): shortest-path var-length semantics + advertised depth clamp
Three of #797's four findings fixed (the fourth — repeated-variable
unification — already holds on main and is now pinned by the test):

- Walk padding: the BFS recursive CTE deduped (node, hop) PAIRS, so a
  single self-loop minted every hop level for every node it could
  reach — *k..k matched fabricated walks of arbitrary length (a
  100-hop 'longest chain' artifact) and the row set exploded to
  nodes x depth (the reported crash). The traversal now returns each
  node once at its MINIMAL hop (MIN/GROUP BY): variable-length matches
  nodes whose shortest path lies in [min,max]. This is deliberately
  shortest-path semantics, not openCypher trail enumeration — right
  for reachability/depth audits on call graphs and linear instead of
  exponential; trace_path shares the traversal and now also reports
  each node once on cyclic graphs.
- Silent 100-hop cap: a clamped hop range now surfaces as
  result->warning ('clamped ... an empty result may mean clamped, not
  no such path'), emitted by query_graph in both TOON and JSON forms,
  on top of the existing log WARN.

Reproduce-first: cypher_exec_varlength_path_semantics_issue797 —
self-loop + real 2-chain fixture; RED on the walk semantics (*2..2
returned 3 endpoints instead of 1), GREEN now, incl. variable
unification, zero rows for nonexistent path lengths, and the clamp
warning assertion.

Closes #797

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 20:58:28 +02:00
Martin Vogel 4382b97908 feat(cypher): coalesce(var.prop, literal) in WHERE clauses
The WHERE parser rejected coalesce() outright ('unexpected operator'),
blocking null-safe numeric filters over optional graph properties —
the exact audit-query shape from the report:
  MATCH (f:Function) WHERE coalesce(f.transitive_loop_depth, 0) >= 2
RETURN-side coalesce already worked; only the WHERE leaf lacked it.

Implementation: the condition leaf gains a coalesce_default literal.
The operator/value tail of condition parsing is factored into a shared
parse_condition_op so both subjects (var[.prop] and
coalesce(var.prop, literal)) accept the full operator set (comparisons,
IS [NOT] NULL, IN). At eval time a missing/empty property value is
substituted with the default before the operator runs. Supported form
is coalesce(var.prop, literal) — matching openCypher's two-argument
null-substitution for this subset.

Reproduce-first: cypher_exec_where_coalesce_issue874 runs the
reporter's exact query — RED (rc=-1 parse error) on the old parser —
and pins both directions: a failing default excludes property-less
nodes, a passing default includes them.

Closes #874

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 19:10:07 +02:00
Martin Vogel 93740cedd6 fix(branch): restore files clobbered by a stale-base soft reset
The previous commit accidentally staged a stale-main vs current-main
delta (a git reset --soft against an outdated origin/main ref),
reverting the #1028 wide-canonicalization work and #1027's regression
test. Restore every touched file to current main; the net branch diff
is now exactly the intended two-file #898 fix (pass_calls.c +
tests/test_mcp.c).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 16:01:04 +02:00
Martin Vogel 51921d9063 fix(pipeline): valid JSON for sequential-path service edges and Route nodes
Two emission bugs in the sequential pipeline (pass_calls.c) — the
parallel path was correct, so the two pipelines produced different
bytes for the same input:

- Brokered ASYNC_CALLS edge props closed the method value's quote but
  not the broker's, emitting "broker":"bullmq} on EVERY such edge;
  the fixup below only repaired truncation, which never fires in the
  normal case. Rebuilt as the parallel path's incremental snprintf.
- create_svc_route_node stored the RAW method/broker string as the
  synthesized Route node's properties (literally 'bullmq' instead of
  {"broker":"bullmq"}). Now emits the parallel path's exact JSON.

Consequences of the malformed rows: json_extract errors across the
query surfaces, the edges generated columns (local_name_gen, url_path
index) fail, and PRAGMA quick_check aborts with 'malformed JSON' —
which since the artifact deep-integrity check (#895) also means such
caches are refused at import.

Reproduce-first: sequential_service_edge_props_are_valid_json_issue898
indexes a small celery fixture (sequential path), asserts >=1
ASYNC_CALLS edge exists (non-vacuous), every edge/node properties blob
is json_valid, and the broker is json_extract-able exactly like the
parallel path emits it. RED on the old emitters (1 malformed edge +
1 malformed Route node), GREEN now.

Note: the reporter's original bullmq JS repro no longer classifies on
current main because CommonJS require()/ESM import resolution has a
separate gap — that is exactly open issue #871; probed and confirmed
while building this fix.

Closes #898

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 15:54:58 +02:00
Martin Vogel 8f0f14b028 Merge pull request #1028 from DeusData/fix/973-wide-canonicalization
fix(win): wide-path canonicalization — drop ANSI _access/_fullpath
2026-07-11 15:30:51 +02:00
Martin Vogel aca02e06ef Merge pull request #1027 from DeusData/fix/773-parser-allocator-epoch
fix(pipeline): destroy TLS parsers at allocator-epoch boundaries (second-index SIGABRT)
2026-07-11 14:13:57 +02:00
Martin Vogel 14198fc1e1 fix(win): wide-path canonicalization — drop ANSI _access/_fullpath (#973)
Three path-canonicalization sites routed UTF-8 paths through the ANSI
CRT (_access + _fullpath): the repo_path canonicalization in
handle_index_repository, project-name derivation in fqn.c, and
resolve_canonical_path (the containment check). ANSI decoding is
locale-dependent by construction — on a CJK system codepage (the
reporter runs Big5) the UTF-8 bytes of a CJK repo path re-decode into
different characters, corrupting canonicalization while our CI (en-US
codepage) sails through: that is why the existing non-ASCII guards
stayed green while v0.9.0 crashed in the field.

New cbm_canonical_path: realpath on POSIX (unchanged behavior); on
Windows a wide GetFileAttributesW existence check + GetFullPathNameW,
never touching the ANSI codepage. resolve_canonical_path now also
requires existence on Windows, aligning it with the POSIX realpath
semantics it mirrors.

Guard: the Windows non-ASCII CLI guard gains the reporter's exact
shape — Traditional-Chinese directory (雷達測試), flag-form
--repo-path + --mode fast, real supervised spawn. Honest caveat: CI
cannot switch the system codepage, so the guard validates the
mechanics under en-US; the locale-specific corruption is fixed by
construction (no ANSI path decoding remains on these sites).

Closes #973

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 14:08:24 +02:00
Martin Vogel 2f7befb9ad fix(pipeline): destroy TLS parsers at allocator-epoch boundaries
SIGABRT (free of a non-libmalloc pointer in ts_stack_delete) on the
second index_repository in one process, whenever a sequential run is
followed by a parallel one. The sequential path parses on the CALLING
thread and left its TLS parser alive; the next parallel run switches
the global tree-sitter allocator to the slab (cbm_slab_install), the
calling thread participates in extraction, reuses the stale parser,
and its per-file teardown frees mimalloc-epoch memory through
slab_free's foreign-pointer fallback -> plain free() -> libmalloc
abort. The v0.9.0 supervisor masks this on the default MCP path (fresh
worker process per index); the in-process path and every embedded
consumer died. Root-caused with allocator-epoch instrumentation: the
victim parser is created at epoch 0 in cbm_pipeline_pass_definitions
and deleted at epoch 1 in extract_worker.

Two boundary fixes:
- run_sequential_pipeline destroys the calling thread's parser at run
  end (also bounds its memory like the parallel workers already do);
- cbm_parallel_extract destroys the calling thread's parser BEFORE
  cbm_slab_install, so no parser ever crosses the allocator switch.

Guards: prod-tier smoke invariant inv_second_index_inprocess
(sequential-then-parallel double index over stdio, RED with signal 6
on the unfixed binary — ASan builds mask the allocator seam, so the
unit tier cannot catch this class) + suite-tier fork-isolated
index_second_inprocess_run_survives_issue773 pinning the in-process
double-index contract. Repro 5/5 clean after the fix (was 5/5 abort).

Closes #773

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 12:51:29 +02:00
Martin Vogel 1a38d0a2e8 Merge pull request #1016 from DeusData/fix/stale-wal-sidecars
fix(store): remove destination WAL/SHM sidecars before installing a fresh DB
2026-07-11 05:36:24 +02:00
Martin Vogel d7e6082be7 Merge pull request #1019 from DeusData/fix/artifact-torn-export
fix(artifact): snapshot exports consistently and refuse corrupt imports
2026-07-11 04:51:02 +02:00
Martin Vogel 4ac5bb6d80 Merge pull request #1018 from DeusData/fix/surface-sqlite-corrupt
fix(store): surface SQLITE_CORRUPT from row scans instead of truncating
2026-07-11 04:36:52 +02:00
Martin Vogel b79c6da931 fix(artifact): snapshot exports consistently and refuse corrupt imports
The FAST export path (the watcher/incremental auto-update) read the
raw main-file bytes of a live WAL-mode store: committed rows still in
the -wal were missing entirely and mid-checkpoint reads produced torn
snapshots (field artifact: four short indexes + btreeInitPage error 11
across ~12 nodes-table pages). Import then installed the torn file
byte-for-byte — the integrity check only sanity-checked the projects
table.

- Export: both quality levels now snapshot via VACUUM INTO (the
  consistent copy); only the index-stripping remains BEST-only, FAST
  keeps zstd-3. The fast path pays a full-copy VACUUM INTO now —
  correctness over speed; a torn team-shared artifact costs far more.
- Import: new cbm_store_check_integrity_deep (shallow check + PRAGMA
  quick_check) — used only on this rare path, hot-path callers keep
  the cheap shallow check.

Reproduce-first: artifact_fast_export_snapshots_live_wal_store
exports FAST while the writer connection is open — RED on the old
code with 0 of 60 nodes surviving the round-trip (all rows lived in
the WAL); GREEN now. store_deep_integrity_detects_page_corruption
pins the import guard both directions (healthy passes, zero-banded
pages refused).

Closes #895

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 03:28:32 +02:00