RFC: Patchset Version Tracking - #311
Conversation
|
Thanks, Derek! One high-level comment: let's drop for now the part which injects prior context into the review context, it's subtle and requires more testing and thinking. It's easy to bias LLMs and make them less good at spotting new issues. The rest overall looks good to me, thank you for working on it. I'll review it a bit more carefully soon. |
|
Hi Roman,
if it helps, we've been seeing it work pretty well at RH. I can try and collect some data by logging into our prod instance. but if it ends up blocking this MR, i'm ok dropping it and hashing out specifics later :) |
|
Yes, let's drop it for now so we can separate infra changes from the review quality changes. Thanks! |
good point. I'll be more discerning of this going forward. |
5c56378 to
ed751db
Compare
|
I'd keep them separate, but not a hard preference. Hardening overall looks good to me. I'll try to merge it soon. |
|
No rush, I want to finish a few more tests. I'll take it out of draft sometime tomorrow. |
Add version, previous_version_id, and commit_range columns to the patchsets table for tracking patch series version chains. The version number is persisted from parse_subject_version() during patchset creation, defaulting to 1 when no version tag is found. Schema changes: - version INTEGER DEFAULT 1 on patchsets table - previous_version_id INTEGER REFERENCES patchsets(id) - commit_range TEXT for forge-originated commit ranges - Partial index on version for efficient v2+ lookups - All migrations are idempotent via try_add_column PatchsetRow gains version, previous_version_id, and commit_range fields. All SELECT queries returning PatchsetRow are updated to include the new columns. create_fetching_patchset() gains optional version and commit_range parameters. The forge webhook caller passes commit_range; other callers pass None for both. Add sanitize_message_id() to src/patch.rs for stripping null bytes and control characters from message-IDs as defense in depth. This commit provides the data model foundation. Version chain linkage (previous_version_id population) comes in the next commit. Assisted-by: Gemini <noreply@google.com> Signed-off-by: derekbarbosa <derekasobrab@gmail.com>
Implement version chain linkage for both mailing list and forge webhook paths. Version linking uses signals (thread match, then author + subject match) with no arbitrary time window. If signals cannot confirm a match, the patchset proceeds independently. Mailing list path: - find_previous_version() searches by thread_id first, then by author + stripped subject (version tags removed via strip_subject_version()). No time window constraint. - create_patchset() populates previous_version_id for v2+ patchsets after creation. - Cross-version merge prevention: patchsets with different explicit versions are not merged even in the same thread. Implicit (None) versions still merge with explicit versions in the same thread to handle series where cover letters have version tags but individual patches do not. - Cross-version patch reassignment blocked in create_patch() to prevent patches from being moved between version-tracked patchsets. Forge webhook path: - find_patchset_by_mr_number() looks up the latest version of an existing MR/PR patchset. - When the same MR fires with a different commit_range, a new version is created with previous_version_id linking to the prior version. - When commit_range matches (metadata-only change like Draft-to-Ready), only title/URL metadata is updated without creating a new version. - update_patchset_metadata() for non-versioning metadata changes. New functions: - strip_subject_version() in src/patch.rs - find_previous_version() in src/db.rs - find_patchset_by_mr_number() in src/db.rs - update_patchset_metadata() in src/db.rs Assisted-by: Gemini <noreply@google.com> Signed-off-by: derekbarbosa <derekasobrab@gmail.com>
Add version and previous_version_id fields to all API JSON responses: - PatchsetRow serialization (list_patchsets) already includes the fields from PLAN-01's struct changes - get_patchset_details() and get_patchset_summary() manual JSON builders updated to include version and previous_version_id - Forge webhook response includes version number Add UI version badge for v2+ patchsets: - .tag-version CSS class with yellow background, light/dark mode - JavaScript renders badge before subsystem tags in the patchset list for versions > 1; v1 patchsets show no badge Add designs/DESIGN_VERSION_TRACKING.md per repository convention for auditability. Assisted-by: Gemini <noreply@google.com> Signed-off-by: derekbarbosa <derekasobrab@gmail.com>
Fix slug generation in forge_webhook version detection to use repo_url instead of pr_url. The pr_url contains the pull/merge request number as its last path segment (e.g., sashiko-dev/pull/42), which produced incorrect slugs like '42-42-v2' instead of 'repo-42-v2'. Add test coverage identified by adversarial review: - find_previous_version: returns None for v1, cross-thread subject matching via author+subject signal - get_previous_version_findings: returns empty when no prior version - format_previous_findings: basic formatting, empty input, token budget truncation Assisted-by: Gemini <noreply@google.com> Signed-off-by: derekbarbosa <derekasobrab@gmail.com>
Wire sanitize_message_id() into create_thread(), create_message(), and create_patchset() to strip null bytes and control characters from message-IDs before database insertion, fulfilling the design requirement for input sanitization at the ingestion boundary. Consolidate the cross-version patch reassignment guard from two separate version queries into a single query using a self-join. Add tracing::info log when a cross-version reassignment is blocked for operational visibility. Assisted-by: Gemini <noreply@google.com> Signed-off-by: derekbarbosa <derekasobrab@gmail.com>
ed751db to
78b7479
Compare
|
v2 -> v3:
|
|
gentle ping for review, but considering the upcoming OOO, may want to hold off on merging this :) |
|
Overall LGTM, but as you said, I'll postpone merging it for a couple of weeks. Sorry for the delay. |
Track version chains for patch series from both mailing lists (NNTP/lore) and forge webhooks (GitHub/GitLab).
When a maintainer posts [PATCH v2 0/5] or pushes new commits to an MR, Sashiko recognizes it as a new version, links it to the prior version, and injects prior review findings into the new review prompt so the AI reviewer can check whether previous issues were addressed.
Schema
Three new columns on the patchsets table: version INTEGER DEFAULT 1, previous_version_id INTEGER REFERENCES patchsets(id), and commit_range TEXT. All migrations are idempotent via try_add_column. Existing patchsets default to version 1 with no chain linkage. A partial index on version > 1 enables efficient lookups.
Version detection
The existing parse_subject_version() function (handles [PATCH v2], [PATCH V3 1/2], [PATCHv5], [PATCH RFC v2 8/8], multi-digit versions) is used to extract version numbers. The parsed version is persisted on patchset creation.
Signal-based chain linkage
find_previous_version() links v2 to v1 using signals, not time windows.
Priority:
(1) same thread_id with version = current - 1
(2) same author + stripped subject match. strip_subject_version() removes version tags from subjects for cross-version comparison.
If no signal confirms a match, the patchset stands alone -- no false positives.
Cross-version merge prevention
Patchsets with different explicit versions are never merged, even in the same thread. Implicit (None) versions still merge with explicit versions in the same thread, preserving the existing behavior where cover letters have [PATCH v6 00/33] but individual patches have [PATCH 01/33]. Patch reassignment between version-tracked patchsets is blocked in create_patch().
Forge versioning
find_patchset_by_mr_number() looks up the latest version for an MR/PR number. When the same MR fires with a different commit_range, a new patchset is created with the version incremented and previous_version_id linking to the prior version. When commit_range matches (metadata-only changes like Draft-to-Ready), only the title and URL are updated without creating a new version.
Prior review context injection
get_previous_version_findings() follows the previous_version_id chain one hop and returns non-preexisting findings sorted by severity. format_previous_findings() produces a structured context string capped at an approximate token limit (2000 tokens, severity-prioritized truncation). The context flows through the reviewer, worker/prompts, and review binary pipeline via the previous_context field on ReviewInput.
API and UI
All API responses include version and previous_version_id fields. The web UI displays a yellow version badge ("v2", "v3") next to patchset names for versions > 1 with light/dark mode support.
Input validation
sanitize_message_id() strips null bytes and control characters from message-IDs as defense in depth.
Depends on: #310 (webhook-input-hardening) -- this adds version chain logic to forge_webhook that trusts webhook payload data (MR numbers, commit ranges) for version decisions. For git-forges, input-hardening's HMAC signature verification and input validation must be in place first to ensure that payload data is authenticated and well-formed before it is used to create version chains or inject prior review findings into AI prompts. This also builds on #310 's revised ForgeProvider trait signature and access control flow.