Skip to content

Feat/srs implementation - #114

Merged
matanp merged 163 commits into
mainfrom
feat/srs-implementation
Jul 29, 2026
Merged

matanp merged 163 commits into
mainfrom
feat/srs-implementation

Conversation

@matanp

@matanp matanp commented Jul 11, 2026

Copy link
Copy Markdown
Owner

No description provided.

Introduces compare-committee-exports and generate-committee-diff-report with xlsx support and a pnpm compare-committees entry point.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Jul 11, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
voter-file-tool-2 Ready Ready Preview, Comment Jul 29, 2026 1:36am

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0b7407e0-17d5-441a-a5e3-fb3af6113af1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/srs-implementation

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.

matanp and others added 26 commits July 28, 2026 21:27
- Create /admin layout.tsx with AuthCheck gate for all admin routes
- Create AdminSidebar with desktop sidebar and mobile Sheet hamburger
- Add adminNav.ts config with enabled/disabled ("Coming soon") items
- Create /admin page.tsx as Data landing page
- Redirect /admin/data and /admin/dashboard to /admin
- Remove redundant AuthCheck from orphaned admin pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract useFileUpload hook for shared R2 upload logic
- Split SpecialReports.tsx into VoterImport.tsx and AbsenteeReport.tsx
- Create ElectionConfigTab.tsx combining Election Dates + Office Names
- Update AdminDataClient with new 5-tab layout:
  Invites | Election Config | Voter Import | Discrepancies | Absentee Report
- Delete SpecialReports.tsx
- Mark all deliverables complete in action items doc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…m (1.1c)

Replace hardcoded capacity check (>= 4) in handleRequest route with
config-driven maxSeatsPerLted from singleton governance config row.
Adds getGovernanceConfig() helper, seed data, and migration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add CommitteeTerm model and migration with termId FK on CommitteeList
- Add getActiveTerm/getActiveTermId helpers in committeeValidation
- Filter committee queries by active term across committees, bulk load, reports
- Add /admin/terms page with TermsManagement (create, set active)
- Add GET/POST /api/admin/terms, PATCH /api/admin/terms/[id]
- Fix ESLint type-safety in terms page, TermsManagement, committeeValidation
- Fix test type issues (fetchLoaded, reportJobs, reports, DateRangePicker, dateRangeValueEquality)
- Refactor error display in AbsenteeReport and VoterImport

Co-authored-by: Cursor <cursoragent@cursor.com>
…rt script

- Add LtedDistrictCrosswalk Prisma model (SRS §4.2)
- Migration for crosswalk table with unique constraint and index
- seedLtedCrosswalk.ts: import script for 2024 LTED Matrix.xlsx
- Monroe County town code → cityTown mapping
- db:seed-lted-crosswalk npm script

Co-authored-by: Cursor <cursoragent@cursor.com>
…kets

- Add tickets 1.2-1.5: committee membership model, membership type, seat model, audit trail
- Add tickets 2.1, 2.1a, 2.3: eligibility validation, email/phone submission, resignation workflow
- Add test specs T1.2 (report generation API) and T1.3 (discrepancy handling)
- Update 1.1c committee governance config and README

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add 1.4 to Depends On in 2.3-resignation-workflow.md
- Update README dependency diagram: 1.4 → 2.3
- Update Tier 2 table Depends on column for 2.3

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add CommitteeMembership model with full lifecycle (SUBMITTED, ACTIVE, REMOVED, etc.)
- Add MembershipStatus, RemovalReason, ResignationMethod, MembershipType enums
- Migrate VoterRecord.committeeId and CommitteeRequest to CommitteeMembership
- Update committee/add, remove, requestAdd, handleRequest APIs
- CommitteeSelector and requests page use CommitteeMembership
- Capacity and duplicate-committee checks via committeeValidation
- Mark ticket 1.2 Done in docs/SRS

Co-authored-by: Cursor <cursoragent@cursor.com>
- Migration: set membershipType=APPOINTED on backfilled ACTIVE records
- API committee/add: accept optional membershipType, default APPOINTED
- API handleRequest: set membershipType=APPOINTED on accept (leader vacancy fill)
- AddCommitteeForm: optional membershipType select (admin only)
- CommitteeSelector + VoterCard: display membershipType badge
- Tests: expectMembershipCreate/Update for add and handleRequest
- testUtils: use Prisma CommitteeMembershipUpdateInput/CreateInput
- .cursorrules: use typed matchers instead of expect.objectContaining

Co-authored-by: Cursor <cursoragent@cursor.com>
Break 1.5 into three implementation sub-tickets: schema & seed,
utility & immutability guard, and route wiring with integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SRS 1.4 — Seat Model: explicit seat slots per committee+term with weight tracking.

Schema & migration:
- Seat model: committeeListId, termId, seatNumber, isPetitioned, weight
- CommitteeList.ltedWeight (Decimal?)
- Migration with backfill from CommitteeGovernanceConfig.maxSeatsPerLted

Seat assignment:
- assignNextAvailableSeat() in seatUtils
- ensureSeatsExist() for new committees (add, handleRequest, bulkLoad)
- seatNumber assigned on membership activation

Weight computation:
- recomputeSeatWeights() when ltedWeight updated
- PATCH /api/committee/updateLtedWeight
- POST /api/admin/weightedTable/import (MCDC Excel bulk import)

Admin UI:
- CommitteeSelector: LTED weight field, seat roster with occupants
- Admin Data: Weighted Table import tab

Docs: 1.4 ticket updated to Done, README index updated
Co-authored-by: Cursor <cursoragent@cursor.com>
- Gap 1: session.user.id falsy → 401 from withPrivilege, no report, no PDF call
- Gap 2: verify x-webhook-signature header and gzip body in PDF API request

Mark T1.2 ticket as Done.

Co-authored-by: Cursor <cursoragent@cursor.com>
- bulkLoadCommittees: auth, VERCEL block, empty/no-discrepancy load,
  import with discrepancies, VRCNUM not found, error handling
- handleCommitteeDiscrepancy: auth, 400/404, accept/reject flows,
  takeAddress, already-resolved idempotency, voter-not-found 500
- Mark T1.3 ticket Done in roadmap

Co-authored-by: Cursor <cursoragent@cursor.com>
Add AuditAction enum (12 actions), AuditLog model with indexes, and
User.auditLogs relation. Seed SYSTEM user for FK integrity on
system-initiated audit events. Mark ticket 1.5a as Done.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the audit log utility function and runtime immutability
enforcement for the AuditLog model per SRS §1.5b.

- logAuditEvent: non-throwing utility wrapping prisma.auditLog.create
  with console.error fallback; exports SYSTEM_USER_ID constant
- auditLogImmutabilityGuard: Prisma middleware blocking update/delete
  on AuditLog, registered in prisma.ts client setup
- 13 unit tests covering utility fields, error suppression, and guard
  enforcement for all blocked operations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add audit logging to add, remove, handleRequest, and requestAdd routes
with before/after snapshots and capacity-rejection metadata. Includes
integration tests for add and handleRequest covering happy path, no-op
skipping, and audit failure resilience.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Mark 1.1, 1.1b, 1.3, 1.4, 1.5 as Done
- Mark T1.2 and T1.3 testing tickets as Done
- Update Implementation Sequence and Combined Timeline
- Point 'Next' to 2.1 Eligibility Validation
- Update 2.1 'What exists today' for backend-enforced already-in-another-committee

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…R.5/1.R.7

Committee API fixes:

- requestAdd now resubmits existing non-active CommitteeMembership rows by updating back to SUBMITTED, clearing terminal fields, and preserving/updating submissionMetadata (1.R.2).

- handleRequest accept now supports replacement flow via submissionMetadata.removeMemberId, removes the replacement target with MEMBER_REMOVED audit logging, and returns 422 when target is missing/non-active (1.R.3).

- add and handleRequest accept now execute capacity check + seat assignment + membership writes in a single transaction, with CommitteeList row locking (FOR UPDATE) and transactional seat utils support (1.R.7).

- handleCommitteeDiscrepancy accept now writes CommitteeMembership/seat state instead of committeeMemberList.connect, including capacity handling (1.R.5).

Bulk import and source-of-truth alignment:

- Refactored bulkLoadCommittees to remove destructive committeeList.deleteMany and deprecated voterRecord.committeeId writes.

- Bulk import now upserts committees, ensures seats, reconciles ACTIVE->REMOVED for absent imports, and creates/reactivates ACTIVE memberships with seat assignment (advances 1.R.4, supports 1.R.5).

- report-server committee mapping now reads active CommitteeMembership + voterRecord instead of committeeMemberList; added dedicated tests.

Test and tooling fixes:

- Added AuditAction enum stubs and transaction mocking in frontend jest.setup to fix audit enum failures (1.R.6).

- Updated/added route tests for requestAdd resubmission, replacement accept path, discrepancy membership activation, and report-server committee mapping helper coverage.

Documentation updates:

- Updated SRS remediation tickets 1.R.2-1.R.7 statuses and acceptance checklists to reflect implemented vs remaining work.

Validation:

- Full workspace test pass succeeded via pnpm test (shared-validators, voter-import-processor, frontend, report-server all green).
matanp and others added 27 commits July 28, 2026 21:29
… shared helper

Pull the SUBMITTED → ACTIVE / REJECTED transaction logic out of
handleRequest and the meeting bulk-decisions route into a single
membershipConfirmation module shared by both.

Behavior changes folded in for consistency across the two confirm paths:
- membershipType now resolves to membership.membershipType ?? "APPOINTED"
  in handleRequest (was hardcoded "APPOINTED").
- Confirm-path audit events (MEMBER_CONFIRMED/ACTIVATED and capacity
  reject) use logAuditEventOrThrow, so an audit-log failure rolls back
  the transaction instead of being swallowed.
- Bulk meeting decisions now run the full confirm path: active-in-another-
  committee rejection, replacement-target handling, and maxSeats-bounded
  capacity check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ge scaffolding

Eliminate the six hand-maintained jurisdiction-scoped report types
(committeeRoster, signInSheet, designationWeightSummary, vacancyReport,
changesReport, petitionOutcomesReport) duplicated across schemas, pages,
forms, and the report grid.

- Add narrow cross-package scopeReportRegistry.ts (label/prisma type/
  filename/format/extraFields); derive SCOPE_REPORT_TYPES, jurisdiction
  labels, and the scope slice of REPORT_TYPE_MAPPINGS from it.
- Collapse generateReportSchema/enrichedReportDataSchema onto a shared
  variant tuple; enforce cityTown at API validation for jurisdiction scope.
- Add frontend SCOPE_REPORT_UI registry + scopeReportFormSpecs, ScopedReportForm,
  ScopedReportPageShell, and loadScopedReportPageData (DB-side Leader filtering,
  fail-closed on missing user id); thin the six pages and delete six *Form.tsx.
- Derive GenerateReportGrid scope cards from the UI registry (minPrivilege
  display-only; auth stays on actual session privilege).
- Preserve auth/data-scope invariants; parity coverage for all six specs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract shared trim-only comparison rules so admission validation and BOE
flagging stay aligned; add sharp-edge and whitespace regression tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
Centralize active-member count and cross-committee checks in committeeValidation so transactional admission paths and preflight eligibility share one implementation.

Co-authored-by: Cursor <cursoragent@cursor.com>
Extract runEligibilityPreflight with prefetched validation inputs, share findActiveTerm and ACTIVE_MEMBERSHIP_STATUS across committee routes, and assert single config/count fetches in tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add the test-type-safety skill and review doc, restore EnrichedReportData discrimination for report-server consumers, and move buildAuditWhere out of the audit route so Next.js typecheck stays valid.

Co-authored-by: Cursor <cursoragent@cursor.com>
…kill

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…isma

Consolidate the duplicated designation-weight rule (frontend and
report-server each carried a hand-maintained copy) into a single pure,
DB-agnostic engine in shared-prisma. Both consumers now delegate via thin
adapters that preserve their existing call shapes and re-export the result
types, so downstream callers are unchanged.

- Add computeDesignationWeight + indexActiveMembershipsBySeat with a shared
  duplicate-seat integrity check; buildSeatRosterRows now indexes occupants
  once and shares the map with the weight engine.
- Add jest + ts-jest and unit tests covering the engine's branches; add
  tsconfig.build.json so tests are excluded from the published build.
- Export Prisma as a value so consumers can use Prisma.Decimal at runtime.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enable add-candidate selection when a removal is already selected so the
Remove or Replace flow works for full committees, with replacement-specific UI copy.

Co-authored-by: Cursor <cursoragent@cursor.com>
Compliance-critical membership, petition, and bulk-import mutations no
longer commit when their audit row fails to write, per SRS 11.1's
immutable-audit requirement.

- Swap logAuditEvent -> logAuditEventOrThrow at the 12 compliance-critical
  sites: requestAdd (2), add (2), petition-outcomes (5), bulkLoadUtils (3).
- Wrap requestAdd's create/resubmit membership write + audit in a single
  prisma.$transaction so an audit failure rolls back the state change.
- Record before/after isPetitioned on the seat PETITION_RECORDED audit.
- Leave reference/config audit sites (crosswalk, jurisdictions, meetings)
  fail-open, each with an inline justification.
- Document intended usage on logAuditEvent / logAuditEventOrThrow.
- Tests: audit-failure paths now assert 500/rollback; add a two-committee
  bulk-import test proving rollback scope is per-committee (earlier
  committees stay committed, batch aborts on the failing one).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parse YYYY-MM-DD ranges as UTC calendar days with a half-open upper bound so events later on the selected end date are included.

Co-authored-by: Cursor <cursoragent@cursor.com>
Deduplicate the two LTED xlsx importers (crosswalk, weightedTable) and
their upload tabs. Shared server lib at lib/lted/ (monroeTownCodes,
digitParsing, xlsxUpload) with the stricter digit parser unified across
both routes and seedLtedCrosswalk.ts; shared client <XlsxUploadCard />
used by both tabs. Per-row business logic stays in each route.

Add a TODO on the weightedTable route to split the normal import from a
bulk import that accepts the currently-unused ltedMatrix param.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract duplicated invite UI formatting and unused-invite predicates into lib/invites so admin, accept, and apply flows stay in sync.

Co-authored-by: Cursor <cursoragent@cursor.com>
Extract shared PresignedUploadReportForm for voter import and absentee report,
and drive admin data tabs from a co-located registry with render functions to
prevent config/switch drift.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace client-side AuthCheck wrappers on admin and authenticated server pages
with server-side checks so Prisma queries run only after authorization.

Co-authored-by: Cursor <cursoragent@cursor.com>
…nges

When admins change maxSeatsPerLted, create missing seats or safely trim excess
vacant seats in the active term, recompute weights, and audit the reconciliation.
Block decreases that would strand active or petitioned seats; require an active
term before allowing max-seat changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce repeatable whole-app review workflows with vector-specific methodologies,
agent skills, scan/freeze/gate scripts, and pnpm review:* commands.

Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce the data-lifecycle retention review vector and tighten methodology
for isolated run directories, explicit scan vector args, and scope-gate rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ectors

Promotes three candidate vectors from FUTURE_CONSIDERATIONS.md into the
review methodology, skill overlays, and scan tooling: frontend state &
interaction correctness, accessibility & mobile operability, and dev
experience & local reproducibility.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Designated Petition reports-hub card and the /petitions page were
gated only at "authenticated", but generation requires RequestAccess
(ReadAccess sits below that bar). A ReadAccess user could see the card,
open the form, submit, and always receive a 403.

- GenerateReportGrid: add minPrivilege=RequestAccess to the Designated
  Petition card so the visibleReportTypes filter hides it from ReadAccess
- petitions/page.tsx: gate to RequestAccess, rendering AdminPageAccessDenied
  for authenticated-but-insufficient while keeping PageSignInRequired for
  logged-out users
- add GenerateReportGrid test covering card visibility by privilege

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace next/font/google's Inter (which fetches from Google at build
time) with the self-hosted @fontsource/inter weights, wired through the
--font-sans CSS variable. Keeps builds fully offline/reproducible while
still actually loading Inter instead of silently falling back to system
fonts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mark the DB/auth-backed pages as force-dynamic so `next build` does not
attempt to statically prerender them (which hits Prisma/auth at build
time and breaks offline builds). Gate Sentry source-map upload and
release creation behind an explicit CI check so local builds stay
offline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mock types

Add error-path cases for the invite GET/apply/pending routes (401/403/404/409/410
and blank-token short-circuit) and for applyPendingInvite (no_invite, missing
jurisdiction scope, unconsumable invite). Replace loose casts in the select mock
with a typed __isSelectTrigger guard, and drop a stale non-null assertion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add POST /api/admin/invites cases for auth (401/403), Leader/non-Leader
jurisdiction validation, existing-user and pending-invite rejection, and
P2002 unique-constraint mapping to a duplicate-invite error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@matanp
matanp force-pushed the feat/srs-implementation branch from ca9af8f to f4dfeb2 Compare July 29, 2026 01:31
@matanp
matanp merged commit 54bc058 into main Jul 29, 2026
5 checks passed

This branch was successfully deployed

1 active deployment
Preview — f4dfeb26 Deployed Jul 29, 2026 by vercel[bot]
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