feat(snippets): body provenance — the cache-with-provenance half of the pointer model (#2688)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Failing after 21s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 53s
CI & Build / Build & push image (push) Successful in 28s

Decision #2686: the recorded location is the source of truth for a
snippet's code; the stored body is a cache of it. data.provenance now
records what the cache is a cache OF — commit_sha + fetched_at — as a
carried JSONB field following the verification precedent, so no
migration is needed and absence keeps today's exact semantics.

The rules: provenance follows the code (fresh SHA restamps it, a code
edit without one drops it, a metadata edit carries it); writes ABOUT
the code carry it — record_verification rebuilds data from scratch and
would otherwise erase it silently; an ok verdict at a known commit
restamps it, since the checker just proved the cache matches the source
there. verify_snippet verdicts also record the commit they ran at,
making "the repo moved on since the check" computable once the forge
integration lands. create/update/verify MCP tools take commit_sha
(git rev-parse HEAD — free for any session with a checkout).

Unit tests pin the compose/carry logic; real-Postgres integration tests
run create→verify→update end-to-end (#2663: DB paths get no
mocked-only coverage).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 12:00:40 -04:00
co-authored by Claude Fable 5
parent 8407368c0c
commit 1e7f66e72d
3 changed files with 268 additions and 2 deletions
+73 -2
View File
@@ -342,7 +342,7 @@ def parse_snippet_fields(
# and copying a blob into the column we index *around* would be pure weight.
_DATA_FIELDS = (
"name", "when_to_use", "signature", "language", "locations", "merged_from",
"verification",
"verification", "provenance",
)
# --- drift check (#2086) -----------------------------------------------------
@@ -398,6 +398,7 @@ def compose_verification(
detail: str = "",
path: str = "",
checked_at: str = "",
commit_sha: str = "",
) -> dict:
"""Build the `data.verification` record. Unknown statuses are rejected here
rather than stored, so the filter never has to cope with a typo'd status."""
@@ -414,9 +415,36 @@ def compose_verification(
out["detail"] = detail.strip()
if (path or "").strip():
out["path"] = path.strip()
# The repo commit the working tree was at when the check ran (#2688). The
# code_sha above expires a verdict when the RECORD is edited; this makes
# "the REPO moved on since the check" computable too, once the forge
# integration can compare it against the current head.
if (commit_sha or "").strip():
out["commit_sha"] = commit_sha.strip()
return out
def compose_provenance(*, commit_sha: str, fetched_at: str = "") -> dict | None:
"""Build the `data.provenance` record: which commit the cached body was
read at, and when.
This is the pointer-model half of decision #2686 — the recorded location
is the source of truth for the code and the stored body is a CACHE of it.
Provenance says what that cache is a cache OF, so a reader (and later the
forge fetch, step 5 of milestone 288) can judge staleness instead of
guessing. Absent provenance is valid and means exactly what every snippet
meant before this existed: a body captured by hand at an unknown point.
"""
sha = (commit_sha or "").strip()
if not sha:
return None
return {
"commit_sha": sha,
"fetched_at": (fetched_at or "").strip()
or datetime.now(timezone.utc).isoformat(),
}
def verification_view(note, fields: dict) -> dict:
"""The verification readout for one snippet, including whether it's expired.
@@ -434,6 +462,7 @@ def verification_view(note, fields: dict) -> dict:
"checked_at": stored.get("checked_at"),
"detail": stored.get("detail"),
"path": stored.get("path"),
"commit_sha": stored.get("commit_sha"),
# What the operator actually wants to know: is there something to fix?
# An expired verdict counts as "needs looking at" even if it said ok,
# since the code it blessed is not the code that's there now.
@@ -451,6 +480,7 @@ def compose_data(
locations: list[dict] | None = None,
merged_from: list[int] | None = None,
verification: dict | None = None,
provenance: dict | None = None,
) -> dict:
"""Build the `notes.data` mirror of a snippet's structured fields.
@@ -479,6 +509,12 @@ def compose_data(
# either: the verdict's code_sha expires it on read if the code moved on.
if verification:
out["verification"] = verification
# Also carried: what commit the cached body was read at (#2688). The caller
# owns the live-or-die rule — update_snippet drops it when the code changes
# without a fresh SHA, because keeping it would claim the new body came
# from the old commit.
if provenance:
out["provenance"] = provenance
# The current code's fingerprint — NOT the code, which stays in the body
# (see _DATA_FIELDS). Its only job is to make "this verdict has expired"
# expressible in SQL: a jsonpath can compare `@.verification.code_sha` to
@@ -616,10 +652,15 @@ async def create_snippet(
locations: list[dict] | None = None,
tags: list[str] | None = None,
project_id: int | None = None,
commit_sha: str = "",
):
"""Create a snippet note (embedded on create for immediate recall). Returns
the created Note. Pass ``locations`` for the multi-location case; the single
``repo``/``path``/``symbol`` are the one-location shorthand."""
``repo``/``path``/``symbol`` are the one-location shorthand.
``commit_sha`` stamps the body's provenance — the commit the recording
session read the code at (#2688). Optional: absent means what it always
meant, a body captured at an unknown point."""
locations = resolve_locations(repo, path, symbol, locations)
note = await notes_svc.create_note(
user_id,
@@ -636,6 +677,7 @@ async def create_snippet(
data=compose_data(
name=name, when_to_use=when_to_use, signature=signature,
language=language, code=code, locations=locations,
provenance=compose_provenance(commit_sha=commit_sha),
),
)
return note
@@ -714,11 +756,17 @@ async def update_snippet(
locations: list[dict] | None = None,
tags: list[str] | None = None,
project_id: int | None | object = UNSET,
commit_sha: str | None = None,
):
"""Partial update: only fields passed (not None) change. Re-serializes the
merged field set back into title/body/tags. Returns the Note, or None if the
id isn't a snippet the caller can see.
``commit_sha`` restamps the body's provenance (#2688). It lives or dies
with the code: passed → restamped at that commit; code changed without it →
dropped, because keeping it would claim the new body came from the old
commit; code untouched → carried.
Share-aware (rule #47/#78): resolves the read scope, then requires WRITE —
so an editor/admin grant lets the holder edit, and a viewer grant does not.
Raises PermissionError when the caller can read but not write, because "not
@@ -761,6 +809,17 @@ async def update_snippet(
else:
merged_locations = cur["locations"]
# Provenance follows the code (#2688): a fresh SHA restamps it; a code
# change without one drops it; an edit that leaves the code alone carries
# it. Order matters — the explicit SHA wins even when the code changed,
# because that is precisely the caller saying where the new body came from.
if commit_sha is not None and commit_sha.strip():
provenance = compose_provenance(commit_sha=commit_sha)
elif code is not None and code != (cur.get("code") or ""):
provenance = None
else:
provenance = cur.get("provenance")
fields: dict = {
"title": compose_title(merged["name"], merged["when_to_use"]),
"body": compose_body(
@@ -782,6 +841,7 @@ async def update_snippet(
# the code, the verdict's code_sha stops matching and it reads as
# unverified from here on — no invalidation branch to get wrong.
verification=merged.get("verification"),
provenance=provenance,
),
}
# Recompute tags: keep any non-language, non-marker tags the note already had
@@ -808,6 +868,7 @@ async def record_verification(
status: str,
detail: str = "",
path: str = "",
commit_sha: str = "",
):
"""Record the result of a drift check against the snippet's source.
@@ -836,6 +897,7 @@ async def record_verification(
checked_code_sha=code_sha(fields.get("code") or ""),
detail=detail,
path=path or fields.get("path") or "",
commit_sha=commit_sha,
)
# Rebuilt from the CURRENT stored fields plus the new verdict, so recording a
# check can't quietly rewrite anything else about the record. Note the body
@@ -850,6 +912,15 @@ async def record_verification(
locations=fields.get("locations") or [],
merged_from=fields.get("merged_from") or [],
verification=verification,
# An "ok" verdict at a known commit IS a provenance claim — the checker
# just established that the cached body matches the source there — so
# it restamps. Any other verdict carries what was known: a verdict is
# about the code, not a change to it, and must not erase it (#2688).
provenance=(
compose_provenance(commit_sha=commit_sha)
if status == VERIFY_OK and (commit_sha or "").strip()
else fields.get("provenance")
),
)
return await notes_svc.update_note(note.user_id, snippet_id, data=data)