Skip to content

fix(grounding): commit the body hash to Markdown, and make the reads that use it agree - #161

Merged
Yashasvi2229 merged 7 commits into
integration/human-team-memory-v1from
fix/grounding-body-hash-in-markdown
Sep 1, 2026
Merged

fix(grounding): commit the body hash to Markdown, and make the reads that use it agree#161
Yashasvi2229 merged 7 commits into
integration/human-team-memory-v1from
fix/grounding-body-hash-in-markdown

Conversation

@Yashasvi2229

Copy link
Copy Markdown
Collaborator

What this fixes

The value that answers "has this grounded code changed?" was captured at
saveCurrentBaseline and written only into _mex_grounded_source inside
.mex/graph.db — a file that is gitignored, disposable by invariant, and whose
deletion the product recommends as a routine repair. So the drift baseline did
not survive mex graph rebuild and never reached a teammate who cloned. The
groundings still resolved, so nothing looked broken; they simply stopped being
able to detect a body change.

bodyHash is now committed to Markdown alongside node and fingerprint.

The field alone would have been inert. Two reads had to change with it, and
the second was found only by running against a real migrated scaffold:

  1. The drift checker took the baseline exclusively from graph.db. After a
    rebuild that row is gone and the branch reported nothing at all — not a
    coarse verdict, nothing.
  2. The same checker read frontmatter.grounds_to, the root key. A scaffold
    wiki migrate has adopted keeps the key under the mex map, so the loop ran
    zero times — as did the hasGroundings gate, which then never opened the
    grounding runtime. On the scaffold this was found on, mex check had never
    checked a single grounding, on any run, and reported clean.

Two diagnostics that named a cause nobody checked are also corrected.

Measured, on a real migrated scaffold

25 files, 27 entities, 16 groundings, graph.db fresh. Identical throwaway
copies, before and after:

before after
capture reports 16 captured, 0 skipped 16 captured, 0 skipped
groundings carrying a bodyHash 0 16
wiki validate warnings 16 0
delete graph.db, rebuild, edit a grounded body 0 GROUNDING_DRIFT 2 GROUNDING_DRIFT

Before this branch the capture printed "16 captured, 0 skipped" and wrote
nothing durable.

Backward compatibility

Optional and additive — a required field would turn every existing scaffold into
a parse error. A grounding with no bodyHash still parses, still validates,
still warns, and against a live index still detects drift from the graph.db
cache, which remains as a documented fallback and still carries the body text
drift review needs for a diff.

A hash that merely differs is never overwritten. That difference is the
drift; re-baselining it silently would erase the finding.

Tests

Five new cases in test/graph-grounding-body-hash.test.ts, each deleting
graph.db and rebuilding it — the thing that used to destroy the baseline.
Two fail on the pre-fix tree and three pass, verified by restoring the base
files and re-running; the three that pass are backward-compatibility pins and
are meant to be insensitive to the fix. Run five times, 5/5 green each time.

Known-red, and not from this branch

release-performance is failing on integration/human-team-memory-v1 already —
the two most recent runs on the branch, including at a9e20df, with 8
runtime.maintenanceMs.* violations and assetViolations: []. Those are graph
and wiki index maintenance timings; this branch touches drift reading and
grounding capture, not indexing. Not addressed here, and deliberately not
worked around: widening a budget is the same move as raising a timeout.

Scope left out

There is no shipped command that only captures baselines — the backfill is
reachable only through mex graph ground, mex sync or mex setup, each of
which launches an agent or is scoped to a new scaffold. For an existing scaffold
whose groundings are correct and merely lack a hash, there is no way to ask for
the one thing needed. Recorded rather than fixed; a new command surface was out
of scope.

The value that answers "has this grounded code changed?" was captured at
saveCurrentBaseline and written only into `_mex_grounded_source` inside
.mex/graph.db. That index is gitignored and disposable by invariant --
`mex graph rebuild` is offered as a routine repair -- so the drift baseline
did not survive a rebuild and never reached a teammate who cloned. The
groundings still resolved, so nothing looked broken; they simply stopped
being able to detect a body change.

Add an optional `bodyHash` to the shared `Grounding` type and populate it at
both graph-lane write sites from the hash saveCurrentBaseline already reads.
Optional and additive because every grounding written before this lacks it,
and a required field would turn each of those scaffolds into a parse error.

`refreshGroundingBaselines` writes it in two cases only: backfill, when
Markdown carries none -- which asserts nothing new, since the graph.db row
written on the same line already holds exactly that value from exactly that
moment -- and re-baseline, when an authorized `updateFingerprints` has just
moved the identity signal too. A hash that merely differs is left alone: that
difference is the drift, and overwriting it would erase the finding.

`persistMovedGroundings` re-derives it on a confirmed rebind, alongside the
fingerprint, because a symbol's name is part of its body.

Two reads had to follow. The drift checker took the baseline only from
graph.db, so after a rebuild it found none and reported nothing at all --
without this the field would have been inert for `mex check`, which is the
command that actually reports drift. It now prefers the committed hash and
falls back to the cache for groundings authored before the field existed.
`isGroundingArray` already tolerated the key and is documented rather than
widened; the round trip through writeGroundings needed no change.

The graph.db row stays. It is now a cache of a canonical value, and it still
carries the body text that drift review needs for a diff.
…graph.db

Four cases, run against the thing that used to destroy the baseline: delete
.mex/graph.db, rebuild it from source, then ask whether an edited constant is
still reported as drift.

Two of them fail on the pre-fix tree and two pass, deliberately. The two that
fail are the fix. The two that pass are the backward-compatibility pins: a
grounding with no committed hash still parses and resolves and simply reports
nothing after a rebuild -- which is the defect stated as a test -- and the
same grounding against a live index still detects drift from the cache, so an
existing scaffold that never rebuilds behaves exactly as it did.

The fourth checks the case that would have been easy to get wrong: a capture
pass run over a grounding whose body has already drifted must not adopt the
new body as the baseline. The difference is the finding.
…d at

`groundingsUnverified` means "there were groundings and not one produced a
verdict", and that has two causes: no graph was supplied, or one was and
nothing could be compared against it. The notice asserted the first
unconditionally, so a reader was sent to inspect a code graph nobody had
looked at -- the same shape as the OperationLogPathError mislabelling, a
message stating a cause nobody checked.

Carry the discriminator. It has to be carried rather than inferred from the
grounding diagnostics, because those are subject to the same bound as every
other diagnostic and a truncated report would lose the evidence.

The wording of the no-graph branch was also wrong, and that is the half that
was costing something. `runValidate` builds its options from serviceOptions,
which carries no graph, so wiki validate has never loaded one and this is the
branch every CLI run takes. "No code graph in this checkout" is a claim about
the repository that the command never checked, and it was printed at a user
whose graph was fresh and 7 MB. It now says what was true of the pass and
names `mex check`, which is the command that does resolve groundings.

That wiki validate cannot check groundings on the CLI at all is a separate
finding and is left as a residue; this commit only stops it lying about why.
… root

Found on a real migrated scaffold, and it makes the rest of this branch inert
for the only population that matters in practice.

`mex check` read `frontmatter.grounds_to` directly. A pre-wiki scaffold keeps
the key there, but once `wiki migrate` adopts a file as an entity, section
13.4 moves it under the `mex` map -- and the direct read then finds nothing.
Measured on a migrated scaffold with a fresh graph and sixteen groundings
across nine files: the root key parsed as null, `extractGroundings` found
four in one file alone, and `mex check` returned zero GROUNDING_ codes of any
kind. Not stale, not missing, not gone. Silent.

That is worse than a false positive, because a scaffold that checks clean is
one nobody looks at.

Two reads, both now going through `extractGroundings`, which resolves the key
path the same way the writer does so the two ends cannot disagree:

  - the checker's own loop, which ran zero times;
  - the `hasGroundings` gate in the drift pipeline, which decided the
    grounding runtime was irrelevant and never opened it, so the checker was
    not even constructed.

The frontmatter value stays as the fallback for a file that cannot be re-read
at that point, which is the only case the old path still covers.

Measured end to end on a copy of that scaffold, before and after. Both report
"16 captured, 0 skipped". Before: 0 groundings carry a body hash, and an edit
inside a grounded function body after a graph rebuild yields 0 issues. After:
16 carry one, and the same edit yields 2 GROUNDING_DRIFT -- two because that
function is grounded from two files.
… gap

The pattern is the part of this round worth reusing: a value that answers "has
this changed?" belongs in Git, the index copy is a cache of it, every reader
must resolve the same key path as the writer, and a fix that ships only the
write half looks complete while doing nothing.

ROUTER records what now works and adds the known issue this round measured but
did not fix: the Wiki CLI's serviceOptions carries no code graph, so validate
cannot resolve a grounding and migrate's backfill never runs. Both degrade
silently.
MALFORMED_GROUNDING is one code over six problems -- a missing node id, a
missing fingerprint, a malformed node id, a malformed fingerprint, a
non-canonical `file`, a bad `verifiedAt`, and finding 39's missing body hash.
Remediation is attached to the code, and the registry text is written for the
first two: "a grounding needs both a code-graph node id and a fingerprint".

The body-hash warning has both. Reported on a real scaffold where all sixteen
warnings carried that line, telling the reader to supply fields that were
already in the file and never naming the one that was absent. It is the same
shape as the notice fixed two commits ago: advice about a cause nobody checked.

`remediation` is already a per-call override on `diagnostic()` and the call
site simply never used it. One option, no new code, no new field, no change to
the registry -- the other five cases keep the text that does fit them.

The replacement names what is missing, says plainly that no amount of hand
editing supplies it, and names the commands that do.
@Yashasvi2229
Yashasvi2229 marked this pull request as draft September 1, 2026 20:31
…he fingerprint

`setup-grounding-e2e` caught this on CI and it is the same coupling this field
exists to break, committed by the writer that introduces the field.

The re-baseline was gated on the fingerprint having changed. Editing a constant
changes the body and leaves the fingerprint identical -- by construction, since
the fingerprint is an identity signal over normalized structure. So on exactly
the edit a body hash is for, the gate never opened, the new baseline was never
written, and drift could never clear. `mex sync` would have reported the same
drift forever after an agent had already repaired the prose.

Authorization is the caller's `updateFingerprints`, which is what actually
carries the intent: `mex sync` passes true after an agent pass and re-baselines
deliberately; `mex ground` and setup pass false and must never overwrite a hash
that differs, because that difference is the finding.

The new tests now pass `updateFingerprints: false` explicitly. That is what
`captureGroundingBaselines` normalizes a missing value to before it reaches
this function, so the tests exercise the real command path instead of the
looser default a direct caller gets.
@Yashasvi2229
Yashasvi2229 marked this pull request as ready for review September 1, 2026 21:16
@Yashasvi2229
Yashasvi2229 merged commit 00b054a into integration/human-team-memory-v1 Sep 1, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant