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
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:
@@ -105,6 +105,7 @@ async def create_snippet(
|
||||
project_id: int = 0,
|
||||
system_ids: list[int] | None = None,
|
||||
force: bool = False,
|
||||
commit_sha: str = "",
|
||||
) -> dict:
|
||||
"""Record a shape in the project's pattern library, so every later
|
||||
instance starts from it instead of re-deriving it.
|
||||
@@ -136,6 +137,12 @@ async def create_snippet(
|
||||
proactively within their project; search finds them across projects.
|
||||
system_ids: Ids of the project's Systems to associate this snippet with.
|
||||
force: Bypass the near-duplicate gate (see below).
|
||||
commit_sha: The commit the code was read at (`git rev-parse HEAD` — you
|
||||
have the repo, so it's free). The recorded location is the source
|
||||
of truth for the code and the stored body is a cache of it; this
|
||||
stamps what the cache is a cache OF, so staleness is judgeable
|
||||
later. Optional, but pass it whenever you're recording from a
|
||||
checkout.
|
||||
|
||||
Returns the created snippet (including a parsed `snippet` field), OR — when a
|
||||
duplicate already exists and force is false — {"duplicate": true,
|
||||
@@ -181,6 +188,7 @@ async def create_snippet(
|
||||
uid, name=name, code=code, language=language, signature=signature,
|
||||
when_to_use=when_to_use, repo=repo, path=path, symbol=symbol,
|
||||
locations=locations, tags=tags, project_id=project_id or None,
|
||||
commit_sha=commit_sha,
|
||||
)
|
||||
if system_ids:
|
||||
await systems_svc.set_record_systems(uid, note.id, system_ids)
|
||||
@@ -295,6 +303,7 @@ async def find_duplicate_snippets(threshold: float = 0.0) -> dict:
|
||||
|
||||
async def verify_snippet(
|
||||
snippet_id: int, status: str, detail: str = "", path: str = "",
|
||||
commit_sha: str = "",
|
||||
) -> dict:
|
||||
"""Record whether a snippet's recorded location and code still match source.
|
||||
|
||||
@@ -329,6 +338,10 @@ async def verify_snippet(
|
||||
path: The path you actually checked, if it differs from the recorded
|
||||
one (e.g. you found the symbol at its new home). Defaults to the
|
||||
recorded path.
|
||||
commit_sha: The commit the working tree was at when you checked
|
||||
(`git rev-parse HEAD`). An "ok" verdict with it also refreshes the
|
||||
body's provenance — you just proved the cached code matches the
|
||||
source at that commit.
|
||||
|
||||
Requires write access: a verdict changes how the record is presented, so
|
||||
being able to read a snippet someone shared with you doesn't let you mark
|
||||
@@ -337,6 +350,7 @@ async def verify_snippet(
|
||||
uid = current_user_id()
|
||||
note = await snippets_svc.record_verification(
|
||||
uid, snippet_id, status=status, detail=detail, path=path,
|
||||
commit_sha=commit_sha,
|
||||
)
|
||||
if note is None:
|
||||
raise ValueError(
|
||||
@@ -359,6 +373,7 @@ async def update_snippet(
|
||||
tags: list[str] | None = None,
|
||||
project_id: int = 0,
|
||||
system_ids: list[int] | None = None,
|
||||
commit_sha: str = "",
|
||||
) -> dict:
|
||||
"""Update a snippet. Only the fields you pass change.
|
||||
|
||||
@@ -374,6 +389,10 @@ async def update_snippet(
|
||||
tags: Replaces the extra-tag set (language + "snippet" are re-derived).
|
||||
project_id: 0 leaves it unchanged, -1 detaches it from its project, a
|
||||
positive id moves it.
|
||||
commit_sha: When you're updating the code from a checkout, the commit
|
||||
it was read at (`git rev-parse HEAD`). Restamps the body's
|
||||
provenance; changing the code WITHOUT it drops the old stamp,
|
||||
since the new body no longer comes from that commit.
|
||||
|
||||
Editing someone else's snippet requires an editor or admin share from them.
|
||||
A read-only share is refused with a message saying so — record your own
|
||||
@@ -394,6 +413,7 @@ async def update_snippet(
|
||||
signature=signature, when_to_use=when_to_use,
|
||||
repo=repo, path=path, symbol=symbol,
|
||||
locations=locations, tags=tags, project_id=project,
|
||||
commit_sha=commit_sha or None,
|
||||
)
|
||||
except PermissionError as exc:
|
||||
# Readable but not writable — surface the real reason, not "not found".
|
||||
|
||||
Reference in New Issue
Block a user