Skip to content

feat: freshness engine Phase 2 — foreground surface (read-only) - #3

Merged
divo12 merged 7 commits into
feat/anchor-drift-invalidationfrom
feat/freshness-foreground
Jul 5, 2026
Merged

divo12 merged 7 commits into
feat/anchor-drift-invalidationfrom
feat/freshness-foreground

Conversation

@divo12

@divo12 divo12 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

What

Phase 2 of the freshness engine: every graph context query now labels each code_verified claim fresh / stale / unknown and quarantines stale ones into a ## Needs re-verification section — computed live against the working tree, zero graph writes. Builds on the merged Phase 1 fingerprint foundation (#2).

Why

Phase 1 stores a per-anchor content fingerprint but nothing surfaces it. Until the agent actually sees that a code_verified fact has drifted, the fingerprint is inert. This is the agent-facing win: the read path distrusts stale facts on every query, without paying to write.

How (backend layering)

  • classifyFreshness — added an explicit unknown state: a resolving anchor whose span can't be hashed right now (unreadable file / resolver error) is flagged unverifiable instead of falsely fresh. Structural/content drift still take precedence; a missing baseline alone stays fresh.
  • anchor-fingerprints.ts (new) — indexFingerprintsByClaim (O(1) lookup after one batched read) + freshnessChecks with a cache-aside stat prefilter (reuse the stored hash when mtime+size are unchanged, else re-hash the span).
  • claim-freshness.ts / context-builderattachFreshness does one batched fingerprintsForClaims for all selected claims (no N+1), returns new objects (immutable, read-only).
  • render.ts — every claim line shows its truth; stale claims move to ## Needs re-verification (omitted when nothing drifted); unknown claims stay in Best Claims with an unverifiable caveat.

Test plan

  • npm test green across all 8 suites, incl. the feat: freshness engine — keep code_verified claims honest as code changes Autoloops/greplica#96 check-anchor-drift regression; tsc --noEmit clean.
  • Tests use real repo/DB/files but route around the embedder (no model download) via the exported attachFreshness/render seams.
  • Verified with a real agent (live): drove the real greplica graph context CLI (real local embedder) through all four verdicts —
    • content drift (incl. an uncommitted edit), structural drift (missing symbol), unknown via chmod 000, and back to fresh on restore;
    • confirmed read-only: after all drift queries, graph read still shows the claim code_verified and graph audit anchors reports no invalidation.

Scope

Phase 2 only. Deferred: Phase 3 background heal (heal-only first), Phase 4 agent re-verify. No doc commits in this PR.


Summary by cubic

Labels each code_verified claim as fresh, stale, or unknown in graph context, shows truth inline, and moves stale items to a “Needs re-verification” section. Computed live against the working tree with zero graph writes.

  • New Features

    • Added attachFreshness to the context builder: one batched fingerprintsForClaims read per packet, stat-prefiltered checks, immutable outputs.
    • Extended classifyFreshness with unknown; structural/content drift take precedence; a readable span with no baseline stays fresh.
    • Updated rendering: show claim truth; quarantine stale claims under “## Needs re-verification”; [STALE]/[UNVERIFIABLE] markers for clarity.
    • Introduced anchor-fingerprints helpers (indexFingerprintsByClaim, freshnessChecks) to reuse stored hashes when mtime+size match.
    • Test wiring: added scripts/check-freshness-foreground.js to npm test; set default freshness: { state: "fresh" } in eval outputs.
  • Bug Fixes

    • The unknown verdict now carries structurally-broken anchors instead of dropping them; strengthened assertions in foreground tests.

Written for commit 9ba971b. Summary will update on new commits.

Review in cubic

divo12 added 5 commits July 5, 2026 16:03
A resolving anchor whose span can't be hashed right now (unreadable file
or resolver error) now yields state:'unknown' instead of a false 'fresh',
so the foreground can flag it as unverifiable rather than vouch for it.
A missing baseline alone (readable span, no stored hash) stays fresh.
Structural/content drift still take precedence.
New anchor-fingerprints module: indexFingerprintsByClaim groups stored
rows by claim (O(1) lookup after one batched read), and freshnessChecks
builds the AnchorCheck[] for a claim's resolved anchors with a cache-aside
stat prefilter — reuse the stored hash when mtime+size are unchanged,
otherwise re-hash the span live.
Add freshness: FreshnessVerdict to ClaimContextResult and a new
attachFreshness step in the builder: one batched fingerprint read for all
selected claims (no N+1), then stat-prefiltered checks -> classifyFreshness
per claim. Returns new objects; the query path performs no graph writes.
Every claim line now shows its truth. Stale claims are quarantined into a
new '## Needs re-verification' section (a distrust signal, omitted when
nothing drifted); unknown-freshness claims stay in Best Claims with an
'unverifiable' caveat.
@divo12
divo12 force-pushed the feat/freshness-foreground branch from 5f3b403 to 5dd425f Compare July 5, 2026 10:33
- anchor-fingerprints: collision-free JSON key (was a space-joined string),
  named StoredByAnchor type, and self-describing helpers (currentSpanHash,
  fileUntouched).
- render: use the named FreshnessVerdict type; replace the emoji caveats with
  plain ASCII [STALE]/[UNVERIFIABLE] markers consistent with the rest of the packet.
- context-builder: unnest the attachFreshness call into a named step.

No behavior change; all checks pass.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found and verified against the latest diff

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread libs/knowledge-graph/code-anchors/freshness.ts Outdated
Comment thread scripts/check-freshness-foreground.js
Comment thread scripts/check-freshness-foreground.js
When a claim mixes structurally-broken anchors with undeterminable ones,
classifyFreshness returned state:'unknown' with broken:[], silently dropping
the broken anchors — inconsistent with the content branch and losing info
Phase 3's healer needs. Now the unknown verdict surfaces them too.

Also harden the foreground content/structural assertions to check state,
not just reason.
@divo12
divo12 merged commit ad4853a into feat/anchor-drift-invalidation Jul 5, 2026
1 check 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