Skip to content

feat: add DAO proposal linking#14

Merged
ECWireless merged 2 commits into
mainfrom
codex/dao-proposal-linking
Jun 10, 2026
Merged

feat: add DAO proposal linking#14
ECWireless merged 2 commits into
mainfrom
codex/dao-proposal-linking

Conversation

@ECWireless

@ECWireless ECWireless commented Jun 10, 2026

Copy link
Copy Markdown
Member

This pull request introduces DAO proposal syncing and linking to the admin quarter transaction workflow, allowing treasury transfers to be associated with DAOhaus proposals. It adds a new database table for proposals, updates the sync process to fetch and match proposals, and enhances the UI to display proposal information and feedback during syncs.

DAO Proposal Integration

  • Added a new dao_proposals table to the database, with unique indexes and a foreign key from treasury_transactions, enabling transactions to be linked to DAOhaus proposals.
  • Introduced new environment variables (DAOHAUS_SUBGRAPH_URL, DAOHAUS_APP_BASE_URL) in .env.example to configure DAOhaus proposal syncing.

Sync Process Enhancements

User Interface Improvements

Navigation and Permissions

  • Added a "Proposals" navigation link to the member home page for users with appropriate permissions.

Database Migration Tracking

  • Recorded the new migration in the Drizzle migration journal.

Summary by CodeRabbit

  • New Features

    • Track and link DAO proposals to treasury transactions; show proposal details and external links alongside transfers
    • New Proposals page listing proposal-linked money movement activity
    • Sync process now attempts proposal linking and reports matched counts; sync UI shows multi-step progress and displays proposal match totals
  • UX

    • Member navigation now shows Proposals link for users with access
  • Documentation

    • Added DAOhaus-related environment variables to config template

Copilot AI review requested due to automatic review settings June 10, 2026 14:16
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
raidguild-accounting Ready Ready Preview, Comment Jun 10, 2026 4:50pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds DAOhaus v3 proposal linking: DB migration and schema updates, a server sync engine matching on-chain BAAL proposals with DAOhaus subgraph data, query APIs for proposal activity, classification/view wiring, admin sync integration, and member/admin UI pages to view proposal-linked transfers.

Changes

Proposal Linking System

Layer / File(s) Summary
Database Schema & Configuration
.env.example, drizzle/0009_wakeful_freak.sql, drizzle/meta/*, src/db/schema.ts
New dao_proposals table and indexes; nullable treasury_transactions.daoProposalId FK with ON DELETE set null; Drizzle snapshot and journal updated; .env.example adds DAOHAUS_SUBGRAPH_URL and optional DAOHAUS_APP_BASE_URL.
DAO Proposals Synchronization Engine
src/lib/dao-proposals.ts
Server module that queries Gnosis RPC and DAOhaus subgraph (with introspection), decodes BAAL processProposal calls, matches proposals to treasury transactions by execution tx hash, merges graph/onchain metadata, upserts dao_proposals, and links treasury_transactions. Exports syncDaoProposalsForPeriod, getDaohausProposalUrl, and DaoProposalSyncResult.
Proposal Activity Query Layer
src/lib/proposal-activity.ts
New listProposalActivity({ visibility }) that joins proposals, transactions, transfers, optional ledger/quarter/entity data; applies visibility filtering; decrypts counterparty; formats timestamps/amounts; and builds explorer URLs.
Transaction Classification View Extension
src/lib/transaction-classification.ts
Extends classification query/view to include daoProposal row; adds TreasuryTransferDaoProposal type and maps joined proposal fields into TreasuryTransferClassificationView.
Admin Quarterly Sync Integration
src/app/admin/quarters/[id]/transactions/actions.ts, sync-transactions-form.tsx, transaction-review-toast.tsx
Calls syncDaoProposalsForPeriod during quarter sync, captures proposal results/errors without aborting treasury sync, extends audit metadata, revalidates /proposals, includes proposal match counts in redirect/query params, and upgrades the sync button to a multi-step progress UI; TransactionReviewToast adds proposalMatchCount prop.
Transaction Review Page Enhancement
src/app/admin/quarters/[id]/transactions/page.tsx
Displays linked proposals on transfer cards via helper components (ProposalTitleLink, ProposalInline, ProposalContext); reads proposals query param and forwards match counts to the review toast.
Public Proposals Page & Navigation
src/app/proposals/page.tsx, src/app/page.tsx
Adds /proposals page showing proposal-linked activity table with DAOhaus and explorer links; formatting helpers; access gated by session permissions; header nav shows "Proposals" for users with canAccess.

Sequence Diagram(s)

sequenceDiagram
  participant Admin as Admin User
  participant SyncForm as Sync Form
  participant SyncAction as syncQuarterTransactions
  participant SyncEngine as syncDaoProposalsForPeriod
  participant GnosisRPC as Gnosis RPC
  participant DAOhaus as DAOhaus Subgraph
  participant DB as Database
  participant Audit as Audit Log

  Admin->>SyncForm: Click Sync Transactions
  SyncForm->>SyncAction: Submit quarterly sync

  par Treasury Sync
    SyncAction->>DB: Sync treasury_transactions
  and Proposal Sync
    SyncAction->>SyncEngine: syncDaoProposalsForPeriod(period)
    SyncEngine->>DB: Select treasury_transactions in period
    SyncEngine->>GnosisRPC: Fetch candidate transactions / decode processProposal
    SyncEngine->>DAOhaus: Introspect & fetch proposals
    SyncEngine->>SyncEngine: Match proposals by execution tx hash
    SyncEngine->>DB: Upsert dao_proposals
    SyncEngine->>DB: Update treasury_transactions.daoProposalId
    SyncEngine-->>SyncAction: DaoProposalSyncResult (counts/errors)
  end

  SyncAction->>Audit: Write audit event (treasury + proposal outcomes)
  SyncAction->>DB: Revalidate /proposals cache
  SyncAction-->>SyncForm: Redirect with proposal match count
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through chains and subgraphs wide,
Matched proposals with transfers side by side,
DBs and RPCs danced in tune,
Now governance whispers trace each rune—
🍃 A rabbit's trail where money and votes collide.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 PR title 'feat: add DAO proposal linking' directly and clearly describes the main objective of the changeset—adding functionality to link DAO proposals to treasury transactions.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 codex/dao-proposal-linking

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 and usage tips.

Copilot AI 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.

Pull request overview

This PR adds DAOhaus proposal syncing and linking into the quarter transaction import/review workflow, persisting proposals in a new dao_proposals table and exposing linked proposal context in both the admin transaction review UI and a new member-facing proposal activity page.

Changes:

  • Add dao_proposals table + treasury_transactions.dao_proposal_id foreign key and indexes to persist proposal metadata and link treasury transactions to proposals.
  • Extend the quarter sync server action to sync proposals for the quarter period, audit the outcome, and refresh the new /proposals page.
  • Update UI to show proposal links inline/contextually in admin transaction review and add a new /proposals activity page for members.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/lib/transaction-classification.ts Extends transfer classification view/query to include optional linked DAO proposal data.
src/lib/proposal-activity.ts Adds server-side query/mapping for proposal-linked transfer activity rows.
src/lib/dao-proposals.ts Implements proposal discovery (subgraph + onchain), upsert into dao_proposals, and linking to treasury transactions.
src/db/schema.ts Introduces daoProposals table and adds daoProposalId reference on treasuryTransactions.
src/app/proposals/page.tsx Adds member-facing proposal activity page UI.
src/app/page.tsx Adds “Proposals” navigation entry for members with access.
src/app/admin/quarters/[id]/transactions/transaction-review-toast.tsx Extends sync toast messaging to include proposal link counts.
src/app/admin/quarters/[id]/transactions/sync-transactions-form.tsx Enhances sync button UX with step-based progress labels.
src/app/admin/quarters/[id]/transactions/page.tsx Displays linked proposal context inline and in transfer details; passes proposal count to toast.
src/app/admin/quarters/[id]/transactions/actions.ts Runs proposal sync during quarter sync, logs audit metadata, and revalidates proposals page.
drizzle/meta/0009_snapshot.json Records updated schema snapshot including new table/column/indexes.
drizzle/meta/_journal.json Adds migration journal entry for migration 0009.
drizzle/0009_wakeful_freak.sql Adds dao_proposals table, treasury_transactions.dao_proposal_id, indexes, FK, and trigger.
.env.example Documents new DAOhaus syncing configuration environment variables.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/app/admin/quarters/[id]/transactions/actions.ts Outdated
Comment thread src/lib/dao-proposals.ts
Comment thread src/app/admin/quarters/[id]/transactions/sync-transactions-form.tsx

@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: 6

🧹 Nitpick comments (5)
src/lib/dao-proposals.ts (2)

130-141: ⚡ Quick win

Consider consistent error handling across configuration getters.

getGnosisClient() throws if GNOSIS_RPC_URL is missing, while getDaoAddress() returns null if DAO_CONTRACT_ADDRESS is missing. This inconsistency could cause confusing errors: if the DAO address is missing, sync gracefully returns skipped: true, but if the RPC URL is missing later, it throws an exception in getOnchainProposalMatches().

Consider either throwing early for both missing configs or handling both as optional with appropriate skip logic.

🤖 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 `@src/lib/dao-proposals.ts` around lines 130 - 141, getGnosisClient() currently
throws when GNOSIS_RPC_URL is missing while getDaoAddress() returns null,
causing inconsistent behavior between getOnchainProposalMatches() and the DAO
sync flow; make them consistent by choosing one approach and implementing it
across the helpers: either (A) make getDaoAddress() throw the same Error as
getGnosisClient() so missing config fails fast, or (B) make getGnosisClient()
return null (or undefined) instead of throwing and update
getOnchainProposalMatches() to detect a null client or address and return {
skipped: true } early. Update all callers of getGnosisClient, getDaoAddress, and
getOnchainProposalMatches to follow the chosen pattern so missing GNOSIS_RPC_URL
or DAO_CONTRACT_ADDRESS are handled identically.

629-679: ⚡ Quick win

Upsert fallback logic is indirect and could be clearer.

The function attempts insert().onConflictDoNothing() which can conflict on either unique index ((chain_id, dao_address, proposal_id) or (chain_id, execution_tx_hash)), then falls back to update() by execution_tx_hash. While this works, it's indirect and makes the intent unclear.

Consider explicit conflict handling
+  const [row] = await db
+    .insert(daoProposals)
+    .values(values)
+    .onConflictDoUpdate({
+      target: [daoProposals.chainId, daoProposals.executionTxHash],
+      set: values,
+    })
+    .returning();
+
-  const [row] = await db
-    .insert(daoProposals)
-    .values(values)
-    .onConflictDoNothing()
-    .returning();
-  const proposalRow =
-    row ??
-    (
-      await db
-        .update(daoProposals)
-        .set(values)
-        .where(
-          and(
-            eq(daoProposals.chainId, gnosis.id),
-            sql`lower(${daoProposals.executionTxHash}) = ${proposal.executionTxHash}`,
-          ),
-        )
-        .returning()
-    )[0];
+  const proposalRow = row;

This makes the intent explicit: upsert by execution hash, which is the unique key for linking to transactions.

🤖 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 `@src/lib/dao-proposals.ts` around lines 629 - 679, The upsert flow in
upsertAndLinkProposal is indirect (insert().onConflictDoNothing() + separate
update by execution_tx_hash); change it to a single explicit upsert by the
execution hash unique key: perform
insert(daoProposals).values(values).onConflictDoUpdate(...) targeting the unique
constraint/key for (chainId, executionTxHash) (use the same
lower-case/normalized expression used elsewhere for executionTxHash) and set the
columns to the incoming values, then use the returned row as proposalRow; keep
the subsequent update to treasuryTransactions as-is
(update(treasuryTransactions).set({ daoProposalId: proposalRow.id }) ...).
Ensure you preserve the normalization (lower()) when matching executionTxHash so
the onConflict target and the where used for matching are consistent.
src/app/page.tsx (1)

137-139: 💤 Low value

Use optional chaining for permission checks inside the nav block.

Line 137 directly accesses session.permissions.canAccess without optional chaining. Although the parent condition at lines 130-132 guarantees session.permissions exists when this code runs, using optional chaining (session.permissions?.canAccess) improves defensive coding and makes the code more resilient to future refactoring. The same pattern applies to lines 140, 143, and 146.

♻️ Suggested pattern for all permission checks
-                {session.permissions.canAccess ? (
+                {session.permissions?.canAccess ? (
                   <AppNavLink href="/proposals">Proposals</AppNavLink>
                 ) : null}
-                {session.permissions.canWriteRaidAccounting ? (
+                {session.permissions?.canWriteRaidAccounting ? (
                   <AppNavLink href="/raids">Raids</AppNavLink>
                 ) : null}
-                {session.permissions.canAdmin ? (
+                {session.permissions?.canAdmin ? (
                   <AppNavLink href="/admin/providers">Providers</AppNavLink>
                 ) : null}
-                {session.permissions.canAdmin ? (
+                {session.permissions?.canAdmin ? (
🤖 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 `@src/app/page.tsx` around lines 137 - 139, The nav block directly accesses
session.permissions.canAccess (rendering <AppNavLink href="/proposals">) —
update these checks to use optional chaining (e.g.,
session.permissions?.canAccess) to guard against undefined permissions; apply
the same pattern to the other permission checks in the same nav block (the
checks at/around lines referencing session.permissions for other links) so all
conditionals use session.permissions?., keeping the existing rendering of
AppNavLink intact.
src/app/proposals/page.tsx (1)

177-179: 💤 Low value

Use optional chaining for defensive access to session.permissions.

Although line 155's early return guarantees session.permissions exists here, using optional chaining makes the code more resilient to refactoring and clearer about null-safety.

♻️ Suggested improvement
   const rows = await listProposalActivity({
-    visibility: session.permissions.canAdmin ? "admin" : "member",
+    visibility: session.permissions?.canAdmin ? "admin" : "member",
   });
🤖 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 `@src/app/proposals/page.tsx` around lines 177 - 179, The call to
listProposalActivity uses session.permissions directly; change it to use
optional chaining on session.permissions (e.g., session?.permissions?.canAdmin)
so the visibility argument is computed defensively, e.g., fallback to "member"
when permissions are undefined; update the invocation at the rows = await
listProposalActivity({...}) site and ensure the ternary uses
session?.permissions?.canAdmin to determine "admin" vs "member".
src/lib/proposal-activity.ts (1)

86-86: 💤 Low value

Redundant WHERE clause after INNER JOIN.

Line 86 filters isNotNull(treasuryTransactions.daoProposalId), but line 68 already inner joins on that FK — any row in the result set must have a non-null daoProposalId. The WHERE clause is harmless but unnecessary and may confuse readers.

♻️ Simplify by removing redundant filter
     .leftJoin(quarters, eq(ledgerEntries.quarterId, quarters.id))
     .leftJoin(entities, eq(ledgerEntries.counterpartyEntityId, entities.id))
-    .where(isNotNull(treasuryTransactions.daoProposalId))
     .orderBy(asc(daoProposals.executedAt), asc(treasuryTransactionTransfers.id));
🤖 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 `@src/lib/proposal-activity.ts` at line 86, Remove the redundant WHERE filter
that checks isNotNull(treasuryTransactions.daoProposalId) in the query chain
inside src/lib/proposal-activity.ts: since the code already performs an INNER
JOIN on the treasuryTransactions.daoProposalId FK (see the join earlier in the
same query), delete the .where(isNotNull(treasuryTransactions.daoProposalId))
call so the query only relies on the INNER JOIN and is clearer; look for the
query chain using treasuryTransactions.daoProposalId and remove that .where
invocation.
🤖 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 `@src/app/admin/quarters/`[id]/transactions/actions.ts:
- Around line 214-217: The query param construction is using the wrong metric:
replace proposalResult.matchedProposals with the linked-count field
(proposalResult.linkedTransactions) when building the URLSearchParams so the
"proposals" param reflects actually linked proposals; update the params creation
(where URLSearchParams is constructed and currently references
result.importedTransfers and proposalResult.matchedProposals) to stringify
proposalResult.linkedTransactions instead, preserving the existing String(...)
conversion and other keys (imported, syncId).

In `@src/app/admin/quarters/`[id]/transactions/sync-transactions-form.tsx:
- Around line 21-31: The useEffect that advances labels while pending doesn't
reset the UI position, so add logic in the useEffect watching pending to reset
stepIndex to 0 whenever a new sync starts or finishes; specifically, inside the
same effect that currently references pending and SYNC_STEPS, call
setStepIndex(0) when pending becomes true (before starting the interval) and
also when pending becomes false (or when cleaning up) so the label returns to
the initial "Syncing treasury…" state; update the effect that contains
useEffect, pending, setStepIndex, stepIndex, and SYNC_STEPS accordingly.

In `@src/app/proposals/page.tsx`:
- Around line 34-51: In formatCurrency, invalid parsed numbers (NaN, ±Infinity)
are currently rendered as "$NaN"/"$Infinity"; update the Number.isFinite check
in the formatCurrency function so that when Number.isFinite(number) is false you
return "-" (same as the null case) instead of returning `$${value}`, preserving
the existing null check and the Intl.NumberFormat formatting path for valid
finite numbers.

In `@src/lib/dao-proposals.ts`:
- Around line 432-438: Replace the non-deterministic fallback in getProposalId
so it does not call crypto.randomUUID(); instead return undefined when no
explicit ID fields are present (i.e., only pickString from
["proposalId","proposalNumber","proposalIndex","serial","id"] and otherwise
undefined). Then update getMatchedProposal and getOnchainProposalMatch to skip
proposals with undefined IDs or, when an execution transaction hash is
available, derive a deterministic ID from that hash (use the
executionTransaction.hash + chain context) so IDs are stable across re-syncs;
ensure all checks and DB uniqueness relies on the deterministic ID or skips the
record when none exists.
- Around line 576-594: getOnchainProposalMatches performs RPC calls sequentially
by awaiting getOnchainProposalMatch inside a for loop; change it to run calls in
parallel with a concurrency limit to avoid blocking the sync workflow. Map each
transaction to a Promise that calls getOnchainProposalMatch and use Promise.all
or a controlled concurrency helper (e.g., p-map, PromisePool, or a simple
batching loop) to await results, then iterate results to set
matches.set(match.executionTxHash, match) for non-null matches; keep the
function signature and return type the same and introduce a configurable
concurrency constant (or parameter) to throttle RPC calls.

In `@src/lib/proposal-activity.ts`:
- Around line 41-43: The helper decryptNullableField currently casts any truthy
value to EncryptedField and calls decryptField, which can throw if the shape is
wrong; update decryptNullableField to first validate the object's shape
(presence and types of algorithm, keyId, iv, tag, ciphertext) or wrap the
decryptField call in a try/catch, returning null on invalid shape or decryption
error; reference the decryptNullableField and decryptField functions and the
EncryptedField shape so the checks specifically guard those properties before
casting or call decryptField inside a safe try/catch block to avoid throwing on
malformed data.

---

Nitpick comments:
In `@src/app/page.tsx`:
- Around line 137-139: The nav block directly accesses
session.permissions.canAccess (rendering <AppNavLink href="/proposals">) —
update these checks to use optional chaining (e.g.,
session.permissions?.canAccess) to guard against undefined permissions; apply
the same pattern to the other permission checks in the same nav block (the
checks at/around lines referencing session.permissions for other links) so all
conditionals use session.permissions?., keeping the existing rendering of
AppNavLink intact.

In `@src/app/proposals/page.tsx`:
- Around line 177-179: The call to listProposalActivity uses session.permissions
directly; change it to use optional chaining on session.permissions (e.g.,
session?.permissions?.canAdmin) so the visibility argument is computed
defensively, e.g., fallback to "member" when permissions are undefined; update
the invocation at the rows = await listProposalActivity({...}) site and ensure
the ternary uses session?.permissions?.canAdmin to determine "admin" vs
"member".

In `@src/lib/dao-proposals.ts`:
- Around line 130-141: getGnosisClient() currently throws when GNOSIS_RPC_URL is
missing while getDaoAddress() returns null, causing inconsistent behavior
between getOnchainProposalMatches() and the DAO sync flow; make them consistent
by choosing one approach and implementing it across the helpers: either (A) make
getDaoAddress() throw the same Error as getGnosisClient() so missing config
fails fast, or (B) make getGnosisClient() return null (or undefined) instead of
throwing and update getOnchainProposalMatches() to detect a null client or
address and return { skipped: true } early. Update all callers of
getGnosisClient, getDaoAddress, and getOnchainProposalMatches to follow the
chosen pattern so missing GNOSIS_RPC_URL or DAO_CONTRACT_ADDRESS are handled
identically.
- Around line 629-679: The upsert flow in upsertAndLinkProposal is indirect
(insert().onConflictDoNothing() + separate update by execution_tx_hash); change
it to a single explicit upsert by the execution hash unique key: perform
insert(daoProposals).values(values).onConflictDoUpdate(...) targeting the unique
constraint/key for (chainId, executionTxHash) (use the same
lower-case/normalized expression used elsewhere for executionTxHash) and set the
columns to the incoming values, then use the returned row as proposalRow; keep
the subsequent update to treasuryTransactions as-is
(update(treasuryTransactions).set({ daoProposalId: proposalRow.id }) ...).
Ensure you preserve the normalization (lower()) when matching executionTxHash so
the onConflict target and the where used for matching are consistent.

In `@src/lib/proposal-activity.ts`:
- Line 86: Remove the redundant WHERE filter that checks
isNotNull(treasuryTransactions.daoProposalId) in the query chain inside
src/lib/proposal-activity.ts: since the code already performs an INNER JOIN on
the treasuryTransactions.daoProposalId FK (see the join earlier in the same
query), delete the .where(isNotNull(treasuryTransactions.daoProposalId)) call so
the query only relies on the INNER JOIN and is clearer; look for the query chain
using treasuryTransactions.daoProposalId and remove that .where invocation.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a18dc988-3624-4628-830f-9db0b42ded94

📥 Commits

Reviewing files that changed from the base of the PR and between adeca30 and 9c8e2bd.

📒 Files selected for processing (14)
  • .env.example
  • drizzle/0009_wakeful_freak.sql
  • drizzle/meta/0009_snapshot.json
  • drizzle/meta/_journal.json
  • src/app/admin/quarters/[id]/transactions/actions.ts
  • src/app/admin/quarters/[id]/transactions/page.tsx
  • src/app/admin/quarters/[id]/transactions/sync-transactions-form.tsx
  • src/app/admin/quarters/[id]/transactions/transaction-review-toast.tsx
  • src/app/page.tsx
  • src/app/proposals/page.tsx
  • src/db/schema.ts
  • src/lib/dao-proposals.ts
  • src/lib/proposal-activity.ts
  • src/lib/transaction-classification.ts

Comment thread src/app/admin/quarters/[id]/transactions/actions.ts
Comment thread src/app/admin/quarters/[id]/transactions/sync-transactions-form.tsx
Comment thread src/app/proposals/page.tsx
Comment thread src/lib/dao-proposals.ts
Comment thread src/lib/dao-proposals.ts
Comment thread src/lib/proposal-activity.ts

@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.

🧹 Nitpick comments (1)
src/lib/proposal-activity.ts (1)

97-100: ⚡ Quick win

Push the member visibility rule into the SQL query.

Line 97 applies the access check after all joined rows have already been loaded. Member requests still read admin-only proposal rows from the database, which does extra work and weakens the data-minimization boundary in this query layer. Apply the published predicate in the query itself instead of post-filtering.

🤖 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 `@src/lib/proposal-activity.ts` around lines 97 - 100, The current post-query
filter on rows (the rows.filter using visibility and row.quarter?.status ===
"published") lets non-admin requests fetch admin-only rows from the DB; instead
push this predicate into the SQL that loads rows so only published quarters are
returned for non-admins. Modify the query builder used to produce rows (the SQL
that joins quarter) to add WHERE (visibility = 'admin' OR quarter.status =
'published') semantics — or more precisely, when visibility !== "admin" add the
condition quarter.status = 'published' — so the filter isn't performed in the
returned rows array.
🤖 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.

Nitpick comments:
In `@src/lib/proposal-activity.ts`:
- Around line 97-100: The current post-query filter on rows (the rows.filter
using visibility and row.quarter?.status === "published") lets non-admin
requests fetch admin-only rows from the DB; instead push this predicate into the
SQL that loads rows so only published quarters are returned for non-admins.
Modify the query builder used to produce rows (the SQL that joins quarter) to
add WHERE (visibility = 'admin' OR quarter.status = 'published') semantics — or
more precisely, when visibility !== "admin" add the condition quarter.status =
'published' — so the filter isn't performed in the returned rows array.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1fbc3c4-d6ff-4b63-8a6c-29ae5d849497

📥 Commits

Reviewing files that changed from the base of the PR and between 9c8e2bd and 410e845.

📒 Files selected for processing (6)
  • src/app/admin/quarters/[id]/transactions/actions.ts
  • src/app/admin/quarters/[id]/transactions/sync-transactions-form.tsx
  • src/app/page.tsx
  • src/app/proposals/page.tsx
  • src/lib/dao-proposals.ts
  • src/lib/proposal-activity.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/app/page.tsx
  • src/app/admin/quarters/[id]/transactions/sync-transactions-form.tsx
  • src/app/admin/quarters/[id]/transactions/actions.ts
  • src/app/proposals/page.tsx
  • src/lib/dao-proposals.ts

@ECWireless ECWireless merged commit 1a6c138 into main Jun 10, 2026
5 checks passed
@ECWireless ECWireless deleted the codex/dao-proposal-linking branch June 10, 2026 16:56
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.

2 participants