Skip to content

feat(recall): review extracted entries from the corpus - #1369

Open
wesm wants to merge 16 commits into
mainfrom
feat/recall-entry-review
Open

wesm wants to merge 16 commits into
mainfrom
feat/recall-entry-review

Conversation

@wesm

@wesm wesm commented Aug 9, 2026

Copy link
Copy Markdown
Member
  • Machine-distilled Recall entries can now be approved or archived from their expanded Corpus row, with local row updates that preserve pagination and scroll position.
  • Approval requires valid provenance and produces an accepted human_reviewed entry; confirmed archive produces an archived human_rejected entry. Both decisions remain outside extraction generation cleanup.
  • Guarded transactional transitions and typed HTTP conflicts prevent stale or repeated decisions. Existing archives shed the SQLite review-state enum while preserving row identities, evidence, supersession links, and search results.
  • Review-state policy now lives at the shared Go write boundary, so adding a business state no longer requires another table constraint change.
  • Recall requires FTS5, as all maintained builds already do. The same archive migration converts old FTS4 entry and evidence indexes once; existing FTS5 indexes are retained. FTS4 initialization and query fallbacks are removed.
  • Read-only backends remain mutation-free, and this slice intentionally excludes editing, bulk review, and undo.
  • Review focus: the archive migration and transaction in internal/db, error mapping in internal/server/recall.go, and row-local behavior in RecallCorpusPanel.svelte.

@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (fd7192a)

The PR has one medium-severity issue: rejected entries disappear from the “Human rejected” view.

Medium

  • frontend/src/lib/components/recall/RecallCorpusPanel.svelte:177 — The “Human rejected” filter does not request archived entries. Because the backend defaults an omitted status filter to accepted while rejected entries are stored with status=archived, rejected decisions never appear in this view.
    • Fix: Add status support to RecallEntryFilters and request status=archived when filtering for human_rejected.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 5m14s

@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (9ff0dc5)

Code review verdict: No Medium, High, or Critical findings were reported.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 6m33s

@mariusvniekerk mariusvniekerk self-assigned this Sep 12, 2026
@mariusvniekerk
mariusvniekerk force-pushed the feat/recall-entry-review branch from 9ff0dc5 to 1fd97d3 Compare September 12, 2026 17:19
@roborev-ci

roborev-ci Bot commented Sep 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (1fd97d3)

Verdict: Changes require fixes for 2 findings.

Medium

  • frontend/messages/ja.json:1292: The new localization keys are missing from the Japanese catalog, while the locale consistency test requires every locale to contain the same keys; this will fail validation and leave the Japanese UI without the new labels. Add Japanese translations for all newly introduced recall review keys.

    Reported by: codex

  • internal/db/recall_review.go:39-45: ReviewRecallEntry only checks writability and can mutate recall entries when archive_content=usage, despite that policy explicitly refusing recall-entry storage and all other recall write paths enforcing requireDerivedTextStorage. Apply requireDerivedTextStorage("recall entries") before the review transaction and map the resulting policy error appropriately at the HTTP layer.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 11s | Total: 10m54s

@roborev-ci

roborev-ci Bot commented Sep 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (fcc0084)

Verdict: Changes require fixes for 2 findings.

Medium

  • internal/db/recall_extract.go:342-347: Activation’s servability check only counts accepted unreviewed_auto entries. After all entries in a retired generation have been human-approved, reactivating that generation reports no servable entries and is blocked even though human_reviewed entries are intentionally preserved and accepted. Count provenance-valid, non-superseded human_reviewed entries as servable during activation.

    Reported by: codex

  • internal/db/recall_extract.go:465-470: The failed-coverage gate treats any archived entry as stale staged output. A durable human_rejected entry is archived by design, so a failed progress row for the same generation/session can incorrectly block generation activation. Restrict the EXISTS clause to archived unreviewed_auto entries, or otherwise exclude human-reviewed states from staged-output detection.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 9s | Total: 14m59s

@roborev-ci

roborev-ci Bot commented Sep 12, 2026

Copy link
Copy Markdown

roborev: Combined Review (fc52e57)

Verdict: Changes require fixes for 2 findings.

Medium

  • internal/db/recall_review.go:44: ReviewRecallEntry bypasses the derived-text storage guard, allowing databases configured with archive_content=usage to mutate and return transcript-derived Recall entries despite other Recall write paths rejecting this policy. Call requireDerivedTextStorage("recall entries") before starting the transaction and map the resulting error to the endpoint’s intended unavailable response.

    Reported by: codex

  • internal/db/recall_review_migration.go:20: The legacy schema migration ignores the caller’s context by using QueryRow and context.Background() for the table copy and swap, so a large or blocked migration cannot be cancelled or respect the startup deadline. Pass the startup context into the migration and use context-aware queries, connection acquisition, and transaction operations throughout.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 9s | Total: 10m24s

@roborev-ci

This comment has been minimized.

@roborev-ci

roborev-ci Bot commented Sep 14, 2026

Copy link
Copy Markdown

roborev: Combined Review (f003164)

Verdict: Changes require fixes for 1 finding.

Medium

  • internal/db/db.go:1627-1674; internal/db/recall.go:842-855, 1647-1655: Read-only opens skip the FTS4-to-FTS5 migration, while Recall text search unconditionally uses the FTS5-only bm25 function. On an existing FTS4 archive opened read-only, the query fails instead of falling back, making Recall search unavailable until a writable open migrates the archive. Detect legacy FTS4 in read-only mode and use a compatible query or LIKE fallback; alternatively return a clear upgrade-required error and classify the FTS4 bm25 error as an unavailable index.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 8s | Total: 10m55s

wesm and others added 16 commits September 15, 2026 19:59
Machine-extracted entries need a durable human disposition before they can safely become trusted or stay rejected. The design keeps that decision explicit in the review state so extraction generation changes cannot silently reverse it.\n\nThe existing SQLite CHECK makes review-state evolution an archive concern instead of a Go business rule. The approved design removes that constraint through one narrowly scoped, data-preserving migration and defines the API, UI, freshness, and failure contracts for the implementation.
The approved workflow spans a data-preserving SQLite migration, transactional review semantics, an HTTP boundary, and row-local frontend behavior. A file-specific red-first plan keeps those layers independently verifiable while preserving the clean scope of individual approve and archive decisions.
Recall review needs a terminal rejection state that survives reopen and future state additions. Keeping the allowed review-state enum in SQLite made each new business state require a table change, so policy now lives at the shared Go write boundary.\n\nExisting archives are rebuilt transactionally before schema initialization while preserving row IDs and relationships needed by evidence, supersession, and FTS.
Machine-generated Recall entries need a one-way human disposition that extraction maintenance cannot reverse. The store now enforces that only accepted, unreviewed automatic entries can be approved or archived, with provenance required for approval.\n\nReturning the hydrated entry from the same transaction keeps the API response aligned with the committed decision, while SQLite triggers continue to invalidate only the query and embedding views affected by each transition.
The Corpus UI needs a narrow mutation boundary for approving or dismissing one machine-generated entry without exposing broader entry editing. The endpoint validates a single explicit action, preserves typed storage conflicts, and returns the committed entry so clients can update one row in place.\n\nRead-only and maintenance states follow the server's existing capability and retry semantics, and vector refresh is scheduled only after a successful decision.
Review controls need one typed client operation and stable user-facing language before the Corpus table can expose mutations. The frontend now models the two allowed decisions, preserves server error details, and names every review state so raw storage values do not leak into the interface.\n\nAll supported locales carry the same confirmation, provenance, and failure messages, keeping the interaction accessible regardless of the active language.
A distilled corpus is only useful if people can turn uncertain automatic output into an explicit decision where they inspect it. Expanded rows now expose immediate approval and confirmed archive actions, while revoked provenance blocks only the trust-increasing transition.\n\nSuccessful responses update the current page locally instead of reloading or disturbing pagination and scroll. Pending and conflict states stay scoped to the affected row so unrelated corpus browsing remains available.
The public workflow now lets users make durable trust decisions from the Corpus table, so the documentation must distinguish approval from rejection and make provenance gating explicit. The extraction lifecycle notes also clarify that machine maintenance cannot reverse either human state.\n\nThe migration test uses the repository's current Go loop form so the full linter remains clean.
The design and execution notes were useful while the review workflow was being built, but they are not product documentation and would add maintenance noise to the public change. The durable behavior now lives in the Recall user and extraction lifecycle documentation.
Human-rejected entries are archived, so querying by review state alone silently restricted the result to the API's accepted default. Pairing that review state with the archived lifecycle status makes the named Corpus filter include the decisions it represents without changing other filters.
The rebase brings in the JSON v2 migration from main. Use its reader API so the review handler compiles while continuing to reject unknown fields and trailing JSON values.
The rebased branch includes Japanese support, but its catalogue was missing the Recall review labels and confirmation messages. Add the translations so Japanese users receive the review copy and all supported locales have matching keys.
Reactivating a generation could fail after all of its entries were approved, or when a failed extraction retained a rejected entry. Count accepted human-approved entries with valid provenance and no superseding replacement as servable, and restrict stale staged-output checks to automatic entries. Human decisions remain unchanged across activation.
Usage-only archives must reject review actions just like other Recall writes. Check the shared storage policy before reading or mutating an entry and report the operation as unavailable over HTTP.

Carry the startup context through the legacy review-state migration so cancellation rolls back the copy. Discard the connection after cancellation to avoid returning connection settings changed by the migration to the pool while rollback completes.
A failed extraction revisit could block reactivation solely because it retained an archived automatic entry with a superseding replacement. Such entries never promote, so they must not count as stale staged output. Match the coverage gate to the promotion predicate while preserving the obsolete entry and its replacement link.
All maintained builds enable FTS5. Keeping an FTS4 fallback added separate query and trigger paths and let the review-state migration attach incompatible triggers to old indexes.

Require FTS5 for Recall and convert old entry and evidence search indexes within the existing archive migration. Preserve source rows and existing FTS5 indexes, and remove FTS4 query paths and their dedicated tests.
@mariusvniekerk
mariusvniekerk force-pushed the feat/recall-entry-review branch from f003164 to f36688f Compare September 16, 2026 00:03
@roborev-ci

roborev-ci Bot commented Sep 16, 2026

Copy link
Copy Markdown

roborev: Combined Review (f36688f)

Verdict: Changes require fixes for 1 finding.

Medium

  • internal/db/recall.go:846: Legacy FTS4 archives opened through OpenReadOnly skip writable migration, but lexical entry search now always calls the FTS5-only bm25() function and does not classify that failure as an unavailable index. Read-only Recall queries against such archives therefore fail instead of returning results or an actionable upgrade error. Detect and reject legacy FTS4 indexes during read-only open with a clear migration-required error, or retain a compatibility/LIKE fallback; ensure FTS4 conversion is not solely gated on the review-state constraint migration.

    Reported by: codex


Reviewers: 2 done | Synthesis: codex, 7s | Total: 14m30s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants