Skip to content

Add permanent lifecycle history for submission reviews - #918

Merged
JoaquinBN merged 3 commits into
devfrom
JoaquinBN/internal-dashboard-spec-review
Jul 10, 2026
Merged

Add permanent lifecycle history for submission reviews#918
JoaquinBN merged 3 commits into
devfrom
JoaquinBN/internal-dashboard-spec-review

Conversation

@JoaquinBN

@JoaquinBN JoaquinBN commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Submission review state is overwritten in place, and re-opening a submission (resubmit, appeal, new evidence) erases who decided it and when. Bulk rejections additionally left no per-decision record at all. This PR makes the review lifecycle fully auditable without changing the review workflow itself.

  • New SubmissionStateTransition append-only log (migration 0079): every lifecycle event is recorded with its actor and from/to states. Covered paths: submission (via a raw-guarded post_save signal), steward review, bulk reject, AI gate reject, submitter edit/resubmit, cancel, appeal, review-field clearing on added evidence, and admin edits (including direct changes to reviewed_by/reviewed_at without a state change). Each state change and its log row commit atomically. Read-only in the Django admin.
  • Bulk reject now writes the same per-submission decision note single rejects do (data.action='reject', data.bulk=true), so bulk decisions appear in the steward note timeline and note-based metrics. The operation now runs in a locked transaction (select_for_update), closing a pre-existing race with concurrent single reviews; the response shape is unchanged and rejected_ids now exactly matches the updated rows.
  • Removed the dead resubmitted_more_info filter from the steward and AI review filtersets and the steward search grammar: re-open paths have cleared reviewed_at since 2026-06-22, so the filter could never match current data. The AI review agent instructions are updated accordingly (unknown params are silently ignored, so a stale caller would otherwise receive the entire unfiltered queue).
  • is:ai-reviewed is now suggested in the steward search autocomplete and help panel (the mapping to has_ai_analysis already existed).

No behavior changes for reviewers beyond the visible bulk-reject notes: states, permissions, points, rewards, proposals, notifications, and response shapes are untouched, and nothing reads the new table yet.

Test plan

  • 9 new tests in contributions/tests/test_state_transitions.py, one per lifecycle path, including bulk-reject notes and admin review-field edits
  • Existing review-flow suites pass: appeal, canceled submissions, steward permissions, steward search, reviewer rewards, calibration fixtures, gate command (198 tests)
  • Frontend parser and analytics tests pass (27); the known pre-existing failures in AuthButton/Profile/meta/api/routes are unrelated
  • manage.py check and makemigrations --check clean

Summary by CodeRabbit

  • New Features

    • Submission review history now permanently records lifecycle decisions; bulk rejections appear as individual rejected entries with matching audit notes.
    • Steward search now supports is:ai-reviewed (including related exclusions) and suggests the new filter.
    • Added filtering for submissions with AI analysis (has_ai_analysis).
  • Bug Fixes

    • Removed the outdated resubmitted-more-info query behavior to prevent incorrect/ignored filtering results.
  • Documentation

    • Updated AI Review guidance and examples to reflect the current filtering and how to identify prior more-info decisions.
  • Tests

    • Added coverage for state transition logging across key lifecycle actions.

Submission review state was overwritten in place, and re-opening a
submission (resubmit, appeal, new evidence) erased who decided it and
when. A new append-only state-transition log now records every
lifecycle event with its actor: submission, steward review, bulk
reject, AI gate reject, submitter edits and cancellations, appeals,
review-field clearing on added evidence, and admin edits (including
direct changes to review fields without a state change). Each state
change and its log row commit atomically, and fixture loads cannot
synthesize log rows.

Bulk rejections now leave the same per-submission decision note that
single rejections do, so they appear in steward note history and in
note-based metrics, and the bulk operation runs in a locked
transaction instead of racing concurrent reviews.

The resubmitted_more_info filter is removed: re-open paths have
cleared review timestamps since June, so it could never match current
data. The steward search autocomplete now suggests is:ai-reviewed in
its place.

## Implementation Notes
- backend/contributions/models.py: SubmissionStateTransition model with
  9 event types, record() helper, and a raw-guarded post_save signal
  that logs creation
- backend/contributions/migrations/0079_submissionstatetransition.py:
  schema for the new table with (submission, created_at) and
  (event, created_at) indexes
- backend/contributions/views.py: transition writes in review, edit,
  cancel, appeal, and add-evidence paths (each atomic with its state
  change); bulk_reject rewritten with transaction.atomic +
  select_for_update, per-submission decision notes (data.bulk=true) and
  bulk_reject transitions; resubmitted_more_info filter removed
- backend/contributions/ai_review/views.py: resubmitted_more_info
  filter removed, unused F import dropped
- backend/contributions/management/commands/review_submissions.py:
  _apply_reject is now atomic and writes a gate_reject transition
  attributed to the AI steward
- backend/contributions/admin.py: read-only SubmissionStateTransition
  admin; save_model logs an admin transition when state, reviewed_by,
  or reviewed_at changes
- backend/contributions/tests/test_state_transitions.py: 9 tests, one
  per lifecycle path incl. bulk-reject notes and admin review-field edits
- frontend/src/components/StewardSearchBar.svelte,
  frontend/src/lib/searchParser.js, frontend/src/lib/searchToParams.js,
  frontend/src/tests/searchParser.test.js: drop the dead resubmitted
  token, suggest ai-reviewed in autocomplete/help
- .claude/skills/ai-review.md: remove the deleted filter from the AI
  review agent instructions (unknown params are silently ignored)
- backend/CLAUDE.md: document the new model and the behavior changes
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an append-only SubmissionStateTransition audit log for submission lifecycle events, including bulk and gate rejections. Updates admin visibility, lifecycle recording, review filters, steward search mappings, tests, and documentation.

Changes

Submission lifecycle auditing

Layer / File(s) Summary
Transition model and persistence
backend/contributions/models.py, backend/contributions/migrations/0079_submissionstatetransition.py
Defines transition events, state fields, actors, indexes, creation logging, and the migration schema.
Lifecycle endpoint event logging
backend/contributions/views.py, backend/contributions/tests/test_state_transitions.py
Records edit, cancellation, appeal, evidence, and steward review events with prior states and actors.
Bulk, gate, and admin event logging
backend/contributions/admin.py, backend/contributions/management/commands/review_submissions.py, backend/contributions/views.py, backend/contributions/tests/test_state_transitions.py
Adds read-only transition administration and records gate or bulk rejections transactionally with decision notes.
Filter, search, and documentation alignment
backend/contributions/ai_review/views.py, backend/contributions/views.py, frontend/src/components/StewardSearchBar.svelte, frontend/src/lib/searchParser.js, frontend/src/lib/searchToParams.js, frontend/src/tests/searchParser.test.js, .claude/skills/ai-review.md, backend/CLAUDE.md, CHANGELOG.md
Removes obsolete resubmission filters, adds AI-analysis filtering, updates search mappings, and documents the new review-history behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Steward
  participant ReviewAPI
  participant SubmittedContribution
  participant SubmissionStateTransition
  participant SubmissionNote
  Steward->>ReviewAPI: review or bulk-reject submissions
  ReviewAPI->>SubmittedContribution: persist rejection state
  ReviewAPI->>SubmissionStateTransition: record transition events
  ReviewAPI->>SubmissionNote: persist bulk decision notes
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: permanent lifecycle history for submission reviews.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch JoaquinBN/internal-dashboard-spec-review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/skills/ai-review.md:
- Line 192: Correct the “Pending submissions resubmitted after more info was
requested” row in the More-Info Filters table by placing the endpoint retrieval
instructions in the Endpoint column and the `internal_notes` inspection criteria
in the Filter column, leaving no content misaligned or unnecessarily empty.

In `@backend/contributions/views.py`:
- Around line 3163-3168: Update the bulk reject operation in the
SubmittedContribution.objects.filter(...).update call to explicitly set
updated_at=timezone.now(), matching the timestamp behavior of the single-review
path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5bf0b2f7-8468-4f1a-8c91-c2f11f600888

📥 Commits

Reviewing files that changed from the base of the PR and between b54fb73 and 60ed943.

📒 Files selected for processing (14)
  • .claude/skills/ai-review.md
  • CHANGELOG.md
  • backend/CLAUDE.md
  • backend/contributions/admin.py
  • backend/contributions/ai_review/views.py
  • backend/contributions/management/commands/review_submissions.py
  • backend/contributions/migrations/0079_submissionstatetransition.py
  • backend/contributions/models.py
  • backend/contributions/tests/test_state_transitions.py
  • backend/contributions/views.py
  • frontend/src/components/StewardSearchBar.svelte
  • frontend/src/lib/searchParser.js
  • frontend/src/lib/searchToParams.js
  • frontend/src/tests/searchParser.test.js
💤 Files with no reviewable changes (1)
  • frontend/src/lib/searchToParams.js

Comment thread .claude/skills/ai-review.md Outdated
Comment thread backend/contributions/views.py
…iews

Bulk rejection updates rows through a queryset update, which skips the
automatic modified-timestamp maintenance that single reviews get, so
bulk-rejected submissions kept a stale updated_at. The bulk path now
stamps updated_at alongside reviewed_at with the same clock reading.
Also realigns a misformatted row in the AI review agent's more-info
filter table so endpoint and filter guidance sit in their own columns.

## Implementation Notes
- backend/contributions/views.py: bulk_reject update() now sets
  updated_at=now using one shared timezone.now() value for both
  timestamps
- .claude/skills/ai-review.md: More-Info Filters row for resubmitted
  submissions puts the endpoint in the Endpoint column and the
  internal_notes inspection guidance in the Filter column

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.claude/skills/ai-review.md (2)

181-183: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clarify the obsolete-filter warning.

An ignored resubmitted_more_info parameter does not produce the entire unfiltered dataset: /api/v1/ai-review/ still applies its documented default pending/unproposed/non-appealed scope. Change “ENTIRE unfiltered queue” to “the endpoint’s default queue without this filter” to avoid misleading agents.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/ai-review.md around lines 181 - 183, The warning about the
removed resubmitted_more_info filter incorrectly says ignored parameters return
the “ENTIRE unfiltered queue.” Update the corresponding guidance text in the AI
review documentation to state that the endpoint returns its default
pending/unproposed/non-appealed queue without this filter, preserving the
instruction not to send the obsolete parameter.

179-179: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Enforce the documented integer constraint. .claude/skills/ai-review.md says min_accepted_contributions is a positive integer, but NumberFilter accepts decimal input and this filter only checks value > 0, so 1.5 will pass. Add integer validation server-side or update the docs to match the actual type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/ai-review.md at line 179, Ensure the documented
positive-integer constraint for min_accepted_contributions matches
implementation: update the associated NumberFilter validation to require a
positive integer, or revise the documentation if decimals are intentionally
supported.
backend/contributions/views.py (1)

1238-1259: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Wrap evidence creation and the review reset in one transaction. Evidence.objects.create(...) runs before the transaction.atomic() block, so a later failure can persist the new evidence while the review-field reset and transition roll back. Move the create into the same atomic section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/contributions/views.py` around lines 1238 - 1259, Move
Evidence.objects.create(...) into the existing transaction.atomic() block that
resets the submission review fields and records
SubmissionStateTransition.EVENT_EVIDENCE_ADDED, ensuring evidence creation and
all related updates commit or roll back together.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.claude/skills/ai-review.md:
- Around line 181-183: The warning about the removed resubmitted_more_info
filter incorrectly says ignored parameters return the “ENTIRE unfiltered queue.”
Update the corresponding guidance text in the AI review documentation to state
that the endpoint returns its default pending/unproposed/non-appealed queue
without this filter, preserving the instruction not to send the obsolete
parameter.
- Line 179: Ensure the documented positive-integer constraint for
min_accepted_contributions matches implementation: update the associated
NumberFilter validation to require a positive integer, or revise the
documentation if decimals are intentionally supported.

In `@backend/contributions/views.py`:
- Around line 1238-1259: Move Evidence.objects.create(...) into the existing
transaction.atomic() block that resets the submission review fields and records
SubmissionStateTransition.EVENT_EVIDENCE_ADDED, ensuring evidence creation and
all related updates commit or roll back together.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ea85ca6e-f2bb-4cec-973a-f332fb7e07f8

📥 Commits

Reviewing files that changed from the base of the PR and between 60ed943 and d4e38dd.

📒 Files selected for processing (2)
  • .claude/skills/ai-review.md
  • backend/contributions/views.py

@JoaquinBN
JoaquinBN merged commit fa36ffe into dev Jul 10, 2026
3 checks passed
@JoaquinBN
JoaquinBN deleted the JoaquinBN/internal-dashboard-spec-review branch July 10, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant