#345 Gate Application Answer Writes and Submission on Application Status - #411
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · needs revision
2 open — 1 🔴 Critical, 1 🟡 Low (see below — neither anchors to a changed diff line)
R1-C1 🔴 Critical — the Vercel check is failing on this PR (build/deploy). Fix: resolve the Vercel build failure — https://vercel.com/sga-operational-affairs-projects/aplio/Gin4cmCYfHqA1TKnaTxHoXhAaoVM
R1-L1 🟡 Low — components/features/application-stepper.tsx#L354 — the "Use profile answers" revert loop still toasts the generic "Failed to revert some answers" when createOrUpdateApplicationAnswer returns the new editability { error } (e.g. a stale tab reverting after the application became non-editable) — inconsistent with the onSave/blur path this PR upgraded to surface the exact refusal sentence. Fix: when a results entry carries a specific { error }, toast that message (mirroring the onSave path) instead of the generic copy.
Revision — Cycle 1fixed R1-L1 · skipped R1-C1 · 7ab6de6 R1-C1: pushed and re-checked, but the Vercel deployment still fails ( |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 2 · needs revision
1 open — 1 🔴 Critical (see below — doesn't anchor to a changed diff line)
R2-C1 🔴 Critical — the Vercel check is failing on this PR (build/deploy). Fix: resolve the Vercel build failure — https://vercel.com/sga-operational-affairs-projects/aplio/F6NhDwtpJAsfPFQsnA1iozMQABg9
7ab6de6 to
63033b7
Compare
Revision — Cycle 2skipped R2-C1 · 63033b7 R2-C1: rebased onto |
Revision — Cycle 5fixed R5-C1 · beb7a04 · rebase: prisma/actions/applications.ts (combined dev's shared format-revalidation refactor with this PR's status-gating check — both additive, no logic dropped) R5-C1 ( Rebase conflict: |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 6 · approved
0 open — clean
beb7a04 to
f9cfb0c
Compare
Revision — rebaseHuman-directed rebase onto
All comment edits made during resolution follow the current
|
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 7 · approved
1 open — 1 🟡 Low (see inline)
| @@ -167,12 +173,19 @@ export async function createOrUpdateApplicationAnswer(params: { | |||
|
|
|||
| const application = await prisma.application.findUnique({ | |||
There was a problem hiding this comment.
R7-L1 🟡 Low — this read stays a bare findUnique({ where: { id } }) + requireOwnership, unlike every other application-by-id query in this file (updateApplicationStatus, withdrawApplication, reopenApplication, deleteDraftApplication, and submitApplication's own write below), which all scope deletedAt: null. Application.deletedAt is currently never set by any action, so this has no live exploit path today, but it's an inconsistency with the plan's Data & contracts section (which specified findFirst scoped to { id, userId, deletedAt: null }) and with the file's own convention. Fix: switch to findFirst({ where: { id: applicationId, userId: currentUser.id, deletedAt: null }, select: { positionId: true, status: true } }) and drop the now-redundant requireOwnership call (miss → throw, matching the IDOR-miss pattern used elsewhere in this file), mirroring submitApplication's read for consistency.
Revision — comment-discipline cleanupApplied the current ENGINEERING §7 comment bar (rare, one line by default, no provenance refs) across this PR's diff. 50c2bdb 7 comments condensed to one line (including removing an |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 8 · approved
0 open — clean
Revision — Cycle 8rebase: lib/constants.ts (auto-resolved, non-overlapping insertion) · f3d56aa |
Pipeline EscalationRebase of File: Since this PR's last rebase, PR #420 (
Both sides touch the same function body, the same read, and the same write. Taking either side whole drops real logic from the other:
Merging correctly requires deciding:
This needs the PR author's (or a maintainer's) judgment, not an autonomous merge. Both conflicting hunks are preserved unresolved; the rebase has been aborted and the branch left untouched at its last pushed commit ( Not auto-resolved; not pushed. |
reads/writes to APPLICANT_EDITABLE_APPLICATION_STATUSES (draft, withdrawn) instead of ownership alone, closing the gap where a submitted application could be silently rewritten and resubmitted. The apply page renders a read-only "already applied" card for non-editable statuses, and the stepper/question components surface the action's exact refusal message via toast instead of a generic one. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Surface the specific refusal message on the "Use profile answers" revert path, mirroring the onSave/blur path's exact-sentence toast instead of the generic "Failed to revert some answers" copy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Applies the project-wide comment-discipline rule (ENGINEERING §7) that landed after most of this PR's comments were written: one line by default, no provenance refs, no narration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revision — Cycle 8e787c36 · rebase: prisma/actions/applications.ts (composed dev's #420 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 9 · approved
0 open — clean
Closes #345
Summary
createOrUpdateApplicationAnswerandsubmitApplicationscoped ownership-only checks — a submitted application (or any status past draft/withdrawn) could be silently rewritten and resubmitted from/positions/[id]/apply, resettingstatus/submittedAt.APPLICANT_EDITABLE_APPLICATION_STATUSES(draft,withdrawn) — the sanctioned withdraw→edit→resubmit path stays open, everything else is refused with a user-facing message.Changes
lib/constants.ts— addedAPPLICANT_EDITABLE_APPLICATION_STATUSESas the single source of truth for both actions and the apply page (kept distinct fromNON_REVIEWABLE_APPLICATION_STATUSES, which encodes a different rule for reviewers).prisma/actions/applications.ts:createOrUpdateApplicationAnswernow does afindFirstscoped to{ id, userId, deletedAt: null }; a miss throws (IDOR-style, unreachable from the UI), a non-editable status returns{ error }with actionable copy.submitApplicationruns the same status check before the window/required-answer checks (so the most relevant message wins), and the final write is anupdateManyscoped to the editable statuses —count === 0returns a distinct "refresh" message for the concurrent-submit race. Return type narrowed toResponseType<void>.createDraftApplicationcomment that claimed a gate existed which was never written.app/(main)/(auth)/positions/[id]/apply/page.tsx— branches onAPPLICANT_EDITABLE_APPLICATION_STATUSESto render either the editableApplicationStepperor a read-only "already applied" card (status badge, submitted date, status-appropriate copy, links to My Applications / back to the position).PageHeaderdescription is omitted for the non-editable case.components/features/application-stepper.tsx— theonSavepassed toApplicationQuestionnow returns the action's{ error }message instead of collapsing it into a barethrow.components/features/application-question.tsx—onSavewidened toPromise<string | void>; a resolved string toasts the exact refusal message and keeps the destructive border without advancing the "saved" ref (so a retry is still attempted later); a throw keeps the existing generic toast.Testing plan
/applications, status Applied./positions/<id>/applyfor that application → "You've already applied" card with the Applied badge and the withdraw instruction; no form fields; both links navigate correctly./positions/<id>/apply→ the editable stepper renders, blurring a field autosaves, Submit succeeds → status Applied with a fresh submitted date (withdraw→edit→resubmit still works)./positions/<id>/applydirectly → still redirects to/positions(unchanged).applicationId(not owned by the caller) → throws (unreachable from the gated UI), not a user-facing{ error }.Automated checks
npm run prettier:check— passnpm run eslint:check— passnpm run tsc:check— passNotes
APPLICANT_EDITABLE_APPLICATION_STATUSESshares members withNON_REVIEWABLE_APPLICATION_STATUSEStoday but encodes a different rule (what the applicant may edit vs. what a reviewer may not act on) — kept as separate constants intentionally, not aliased.