fix(knowledge): the browse vocabulary catches up three kinds, and a snippet's mirror survives the generic door (#3128 recs 2-6)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 27s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 1m6s
CI & Build / Build & push image (push) Successful in 33s
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 27s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 1m6s
CI & Build / Build & push image (push) Successful in 33s
Spike #3128 found the storage sound and the retrieval vocabulary frozen before `issue` shipped (0065). Five things, in the order they had to land. **The mirror (rec 5, the data-integrity one).** `notes.data` is DERIVED from a snippet's body, but only `update_snippet` knew that. `update_note` is a hasattr loop with no snippet awareness, and both doors reach it — so PATCH /api/notes/<snippet_id> {body} rewrote the body and left the mirror behind. `snippet_fields` PREFERS the mirror, so the row went on reporting its old repo/path/symbol to the location reverse lookup and to prior-art recall while displaying its new body: surfaced with full authority, and wrong. `snippets.recompose_data` rebuilds it from the body, carrying `verification` and `provenance` (neither is in the body to parse). An explicit `data` still wins, so every snippet-service write is untouched. **One facet table (rec 3), before adding any facet.** The type predicate was written three times — SQL, Python over semantic candidates, and a ternary computing the `is_task` pre-filter — and agreed only by luck. Adding `issue` to the SQL arm alone would have set the pre-filter to is_task=False, handed the Python arm a candidate set with no tasks in it, and returned an empty semantic half for the Issues facet forever with nothing red. `_FACETS` now generates all three. The Python arm also regains the `status IS NULL` half its SQL twin always had. **Issue and spike become facets (rec 2).** 435 issues — 17% of every task — were filterable nowhere on the human surface, while retired `plan` (90 rows) had a chip of its own. `_VALID_TYPES` was a hand-kept copy and is now derived. `plan` stays a valid facet for its legacy rows; it loses its chip. **Snippets stop being half-present in the feed (rec 4).** All 90 were in the All list, in no count, wearing an empty badge, and opening in the note editor. Counts now group by task_kind — every kind for the same two round-trips, which is why `issue` had no number — and total includes snippets, so the All chip matches the list it labels. Snippet cards route to /snippets/:id. **The prose that excused it (rec 6).** `snippet_fields` and the `data` column both still said pre-0070 rows were "never backfilled". True when 0070 landed, false since `backfill_snippet_data` shipped, and it read as licence for a stale mirror. Tests: the pre-filter can never exclude a row its own facet accepts (the regression, parameterised over every facet); both dialects select exactly their own rows; an unknown facet matches nothing; the mirror follows a body or title write, carries the verdict, and yields to an explicit `data`. `compiled_sql` moves to tests/helpers rather than becoming a third copy. Write-up: note #3161.
This commit is contained in:
@@ -540,14 +540,60 @@ def compose_data(
|
||||
return out
|
||||
|
||||
|
||||
def recompose_data(note) -> dict:
|
||||
"""Rebuild a snippet's `data` mirror from its own body, title and tags.
|
||||
|
||||
For the GENERIC note door. `update_snippet` composes the mirror itself from
|
||||
the field set it just merged and never needs this; a plain
|
||||
`update_note(body=...)` — which the Knowledge feed's editor issues, because
|
||||
a snippet card there routes to /notes/:id — has no idea the mirror exists,
|
||||
and left it stale. `snippet_fields` then PREFERS the stale mirror, so the
|
||||
row reported its old repo/path/symbol to prior-art recall while displaying
|
||||
its new body: confidently wrong, which is worse than no record (#3128).
|
||||
|
||||
The body is the authority; the mirror is derived. That is already the rule
|
||||
this file states — it just had no enforcement on the path that bypasses
|
||||
`update_snippet`.
|
||||
|
||||
`verification` and `provenance` are CARRIED, not recomposed, because
|
||||
neither is in the body to parse — the same carry `compose_data` does for
|
||||
the snippet service's own writes. Note that a verdict does not need
|
||||
invalidating here: `code_sha` is recomputed from the new code, so a stale
|
||||
verdict expires itself on read exactly as it does after any other edit.
|
||||
"""
|
||||
parsed = parse_snippet_fields(note.title, note.body, note.tags)
|
||||
prior = note.data or {}
|
||||
return compose_data(
|
||||
name=parsed["name"],
|
||||
when_to_use=parsed["when_to_use"],
|
||||
signature=parsed["signature"],
|
||||
language=parsed["language"],
|
||||
code=parsed["code"],
|
||||
locations=parsed["locations"],
|
||||
merged_from=parsed["merged_from"],
|
||||
verification=prior.get("verification"),
|
||||
provenance=prior.get("provenance"),
|
||||
)
|
||||
|
||||
|
||||
def snippet_fields(note) -> dict:
|
||||
"""Structured fields for a snippet, preferring the indexed `data` column and
|
||||
falling back to parsing the body.
|
||||
|
||||
Both paths must agree, because rows written before migration 0070 have no
|
||||
`data` and are never backfilled — a hand-edited body is the authority for
|
||||
those, and there is no deadline by which they must be converted. `code` only
|
||||
ever comes from the body, since `data` doesn't carry it.
|
||||
THE BODY IS THE AUTHORITY; `data` is a mirror derived from it. Every writer
|
||||
keeps them in step — the snippet service composes the mirror from the field
|
||||
set it just merged, `update_note` recomposes it when a body reaches the
|
||||
generic door (#3128), and `backfill_snippet_data` filled the pre-0070 rows
|
||||
at startup. The fallback below is therefore a belt to that braces, not a
|
||||
second source of truth: it is what a row looks like before the backfill has
|
||||
run, and it must keep agreeing with the mirror.
|
||||
|
||||
(This docstring used to say those rows were "never backfilled". That was
|
||||
true when 0070 landed and stopped being true when the backfill shipped; it
|
||||
is corrected here because the sentence read as licence for a stale mirror,
|
||||
which is exactly the bug #3128 found.)
|
||||
|
||||
`code` only ever comes from the body, since `data` doesn't carry it.
|
||||
"""
|
||||
parsed = parse_snippet_fields(note.title, note.body, note.tags)
|
||||
stored = getattr(note, "data", None)
|
||||
|
||||
Reference in New Issue
Block a user