The memory-core ratchet (lint-memory-core) failed on the publication
fence commit: it added six raw free() calls, each releasing a block that
existing code allocates with raw calloc, malloc or strdup. cbm_free cannot
release such a block without the core class counters drifting, so every
new exit now reuses an existing release point instead:
- index_supervisor.c: worker_artifacts_create() creates the response, log
and publish fence files and names the one that failed, so all three
failures share the single artifact_create_failed exit.
- pipeline.c: a refused publication gate takes the existing source-changed
abort exit (same CBM_PIPELINE_ABORT_PRESERVE_DB result and cleanup).
- mcp.c: index_request_names_free() is the one release point for the
request name strings, used by index_worker_json_result() and
index_args_serving_index_servable().
Raw-site counts return to the origin/main values (index_supervisor.c 8,
pipeline.c 129, mcp.c 765) with no baseline change. No behaviour change.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Second maintainer refresh of #1561 (truthful index_status freshness),
onto main 9c19b5dd, after the fix-up that keeps the indexed checkout in
its own index_checkout table (a0d0521f). The first refresh is b44a22ca.
Five files conflicted; each is resolved as main plus the branch change:
- src/pipeline/pipeline.c and src/pipeline/pipeline_incremental.c (two
sites): main added .unresolved_calls_complete to the coverage-meta
initializers where the branch added .indexed_checkout_sha. Both fields
are kept, main first.
- src/store/store.h cbm_coverage_meta_t: main added
unresolved_calls_complete where the branch added indexed_checkout_sha.
Both fields are kept, main first.
- src/store/store.c init_schema: main added the unresolved_calls_complete
column where the branch closed index_coverage_meta and created
index_checkout. index_coverage_meta keeps main DDL exactly, and the
index_checkout table follows it.
- src/store/store.c coverage replace: main moved the coverage-meta write
out of cbm_store_coverage_replace_ex into coverage_write_metadata,
whose caller owns the transaction and rolls it back on failure. The
branch wrote or cleared the index_checkout row right after that
write, so its two calls move into the helper: the upsert path returns
index_checkout_write(s, project, meta->indexed_checkout_sha), the
delete path returns index_checkout_write(s, project, NULL).
index_checkout_write stays where the branch defined it, ahead of the
new helpers.
- tests/test_store_nodes.c: main added
store_coverage_shadow_excludes_unresolved_calls and its helper where
the branch added store_coverage_meta_indexed_checkout_sha_roundtrip_and_rollback.
Both tests are kept and registered, main first.
src/pipeline/pipeline_internal.h merged without a conflict: main raised
CBM_SEMANTIC_INDEX_VERSION to 13 for Next.js App Router routes, in a
separate hunk from the branch's cbm_pipeline_indexed_checkout_sha
declaration.
No semantic conflicts:
- The branch bumps no version. index_checkout is created by init_schema
with IF NOT EXISTS and read only when present, so main's semantic
index version 13 applies unchanged.
- The branch changes no existing function signature, and main changes
none that the branch calls.
- Every main caller of cbm_store_coverage_meta_get added since the
merge base, including the new Next.js upgrade test in test_pipeline.c,
releases the result with cbm_store_coverage_meta_clear exactly once,
which frees the checkout string. That test passes a shallow copy of
the result to cbm_store_coverage_replace_ex and never frees the copy
on its own.
No test call site needed adapting.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Removing the only Hermes pre_llm_call hook empties the hooks mapping, and
the mapping editor writes an emptied section as hooks: {} - the same
convention it uses for mcp_servers: {}. The hook sequence editor then
refused that file for both upsert and removal, because it required every
parent on the sequence path to be a block mapping header. On a config
where our hook was the only entry under hooks, install -> uninstall ->
install exited 1, and a repeated uninstall did too.
An exact empty flow mapping on a parent of the sequence path is now read
as that empty mapping, mirroring how the mapping-entry editor already
treats section: {}. Removal is a byte-identical no-op, and upsert expands
the header in place, keeping its comment, the line endings and every
other byte, so a reinstall lands back on the first install byte for
byte. The sequence key itself holding a mapping, any other flow value,
and content nested under the inline mapping stay refused. Hooks written
by the user and every foreign key are left untouched.
Found while finishing #1862: its persisted hook opt-out runs this remover
on every later install, so every Hermes user whose only hook was ours
would have hit the refusal on each install after the first.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings the branch up to main 9c19b5dd.
Two same-spot insertion conflicts, each resolved as the main version
of the file plus the own diff of this branch:
- src/daemon/application.c: the job struct gained watch_owners on main
and request_started_ms plus the request facts here; both kept.
- tests/test_daemon_application.c: main added the #1818
autoindex-skipped helpers where this branch added the worker resource
failure test; both kept, the test placed after the doc_candidates
test.
Everything else merged cleanly. src/main.c changed on both sides: main
(#2838) lowers the worker CPU priority inside the worker role block,
and this branch strips the publication fence flag before that block
and installs the fence after the worker role options; the hunks are
disjoint and both are kept. tests/test_pipeline.c gained the Next.js
route tests (#2747) on main and the publication gate test here, also
disjoint. No call site needed adapting: no new main code calls a
function this branch re-signed, and main re-signed none that this
branch calls.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
index_coverage_meta is one of the tables the graph digest pins column
by column, so the new indexed_checkout_sha column made the digest
reject every live store with a schema error: the live-schema
compatibility test failed, and test-impact, which digests the graph it
builds for HEAD, fell back to running every test for every change.
Record the checkout in a table of its own instead: index_checkout
(project primary key referencing projects ON DELETE CASCADE,
indexed_checkout_sha NOT NULL). A checkout is source identity, which
the graph digest deliberately does not bind, and the store already
keeps other per-project data (doc_link_*, observed_*) outside the
digested tables. index_coverage_meta keeps exactly the shape the digest
expects, so the digest needs no change and no version bump, and a new
table needs no migration of existing databases.
The row is written and cleared in the same coverage transaction, so a
failed replace keeps the previous identity, and it is removed with its
project. The store API is unchanged: cbm_coverage_meta_t carries
indexed_checkout_sha as before. Read-only opens of databases without
the table report no identity, as before.
store_graph_digest_indexed_checkout_identity_is_not_bound writes a
store through the real coverage API with and without a checkout SHA
and asserts that it still digests and that the SHA leaves the digest
unchanged.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A worker resource limit is enforced by the supervisor, but only the worker
replaces the serving database, and it does so before the persistence
artifact export. A limit observed in that window stopped a worker that had
already published, and the response still said serving_index_preserved=true
while the previous index had been replaced.
Publication and resource termination now coordinate through a per-attempt
publication fence: a never-created path next to a reserved unique file in
the private worker directory, which both sides claim by creating it
exclusively (cbm_fopen "wbx": O_EXCL on POSIX, CREATE_NEW on Windows), so
exactly one of them can win.
- The worker claims it through a new pipeline publication gate that
cbm_pipeline_run calls once, after the last pre-publication check and
before the serving database is touched. Losing means the supervisor
already stopped the attempt for a limit: the run aborts before
publication and the previous generation stays in place.
- The supervisor claims it before it terminates the worker for a limit or
a failed RSS probe. Losing means the worker already committed to
publishing: the limit is no longer enforced for that attempt, which runs
to completion, and index.supervisor.resource_limit_after_publication is
logged instead. A claim that can be neither won nor lost is retried on
the next poll rather than risk stopping a committed worker.
- At terminal the supervisor settles the fence into
result.publication_committed. An unquiesced tree keeps the fence, so a
surviving member can never publish after the report.
serving_index_preserved now describes the request, not only its last
attempt: a servable index existed when the request began, no attempt of
the request committed to publishing (an earlier attempt can publish and
then crash into recovery), and that index is still servable. The direct
path and the daemon job both fold every attempt into these request facts;
cbm_mcp_index_worker_resource_response takes them as a third argument, and
NULL claims nothing.
The fence path travels as an optional last pair of the exact worker grammar
(--index-worker-publish-fence), accepted by the worker argv parser and by
the daemon bootstrap role check, stripped by the CLI and installed by both
worker entries.
Limits stay opt-in: without index_max_rss_mb or index_max_duration_seconds
there is no fence, no extra argv, no claim and no extra database read.
Graph content is unchanged either way.
RED (c78c077a plus only the new end-to-end test): a FIFO at the artifact
temp path holds the real worker after the rename, and the duration clock
crosses its limit only once the serving database has been replaced. The
supervisor stops the worker there and answers resource_limit_exceeded with
serving_index_preserved=true over a replaced database. GREEN: the worker
claimed first, the supervisor logs the limit as not enforced, and the
released worker finishes and reports the published index.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
cppcheck 2.20 (the CI version) reports shadowFunction: the local
cache_dir in cbm_mcp_ignore_worktrees_runtime_enabled() shadows the
static cache_dir() helper in mcp.c. Rename the local to store_dir; no
behavior change.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings the branch up to main a5b248f47. Eight files conflicted, each a
same-spot insertion where main and this branch added lines next to the
same anchor. Each resolves to the main version plus the change of this
branch:
- src/cli/cli.h, src/cli/cli.c: CBM_CONFIG_IGNORE_WORKTREES and its
CONFIG_KEYS row next to the new doc_candidates key.
- src/mcp/mcp.c: the index_worktree schema property next to the new
doc_candidates property.
- README.md, docs/CONFIGURATION.md: the ignore_worktrees line and table
row next to the rewritten auto_watch entries and the new
doc_candidates and watch_non_git rows.
- src/git/git_context.c: the env-isolation comment after the new debug
stage marker, directly above cbm_popen_git.
- src/cli/hook_augment.c: the new SubagentStart cli_hint ahead of the
parent-graph check of this branch.
- tests/test_mcp.c: both RUN_TEST blocks; main already includes
str_util.h.
Per file, the lines changed against main equal the changes of this
branch against the merge-base.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The review of 2026-10-01 asked for this to be a stated decision rather
than a side effect. Detection walks up from the given path to the
nearest .git entry, so a subdirectory of a linked worktree is treated
as that worktree: index_repository on it is refused, and a session root
or hook working directory inside it is handled the same way. The walk
stops at the first .git; outside any checkout it runs to the filesystem
root one directory at a time. None of it runs while the key is off.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The lifecycle hook now reads ignore_worktrees first and does nothing
else for it while the key is off; the project lookup is then the one
the hook did before the key existed.
- cbm_mcp_ignore_worktrees_runtime_enabled() replaces
cbm_mcp_server_attach_runtime_config(). It never attaches a store to
the server, which may be the daemon session server. A server with a
store answers from it. Otherwise the runtime store is opened only to
read this key, and only when it already exists, so a hook event never
creates _config.db.
- cwd canonicalization, the linked-worktree probe and the parent-graph
check moved into ha_resolve_project_ignoring_worktrees(), which runs
only when the key is on.
Test cli_hook_ignore_worktrees_default_off_does_no_worktree_work was red
on the previous head (a default-off event created _config.db). It also
pins, through a test-API counter, that neither an absent store nor a
store without the key reaches the worktree gate, and that the key on
reaches it once. mcp_ignore_worktrees_runtime_read_leaves_server_and_store_alone
replaces the test of the removed helper.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The review of 2026-10-01 asked to split these out of the ignore_worktrees
change. Neither belongs to it, and both are already on main in another
form, so this restores the three files to the branch merge-base:
- Makefile.cbm: main links cbm-fuzz with -fsanitize-link-c++-runtime.
- src/cypher/cypher.c and tests/test_cypher.c: main frees a partially
parsed MATCH pattern since 70543ba82, with its own regression test
cypher_parse_error_frees_partial_pattern.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Brings the branch up to date with main (a5b248f4). No conflicts.
scripts/test.sh keeps Step 0e4 next to Step 0e3; the gate script and
its test are unchanged by main.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The test only covered a whitelisted finding. The defect in #2442 is
the other direction: on main clang-tidy reported findings that carry
the ,-warnings-as-errors suffix, the old FINDING_RE matched none of
them, and the gate reported clean. Pin that direction too: a second
fixture function with no whitelist entry, a suffixed finding inside
it, and the gate must exit 1 and report the finding under its plain
check name ([clang-analyzer-core.NullDereference], the whitelist key
form) as an entry missing for unlisted().
With the suffix group of FINDING_RE reverted, this case exits 0 with
"memory gate clean" and the test fails, naming all three assertions.
A parser that accepts the line but keeps the suffix inside the check
name fails it as well. The negative assertion is a maintainer
decision (R1).
Refs #2442
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>