{"record":{"id":"d8cb59870699be00","repo":"instructure/canvas-lms","slug":"peer-review-override-due-date-cannot-be-before-parent","errorCode":null,"errorMessage":"Peer review override due date cannot be before parent override available from date","messagePattern":"Peer review override due date cannot be before parent override available from date","errorType":"validation","errorClass":"PeerReview::InvalidDatesError","httpStatus":null,"severity":"error","filePath":"app/services/peer_review/validations.rb","lineNumber":171,"sourceCode":"    if parent_unlock_at && parent_due_at && parent_unlock_at > parent_due_at\n      raise PeerReview::InvalidDatesError,\n            is_override ? I18n.t(\"Parent override due date cannot be before parent override available from date\") : I18n.t(\"Assignment due date cannot be before assignment available from date\")\n    end\n\n    # Validate: child unlock_at constraint\n    # If parent_due_at is present, validate: parent due_at <= child unlock_at\n    # Otherwise, fall back to: parent unlock_at <= child unlock_at\n    if parent_due_at && child_unlock_at && parent_due_at > child_unlock_at\n      raise PeerReview::InvalidDatesError,\n            is_override ? I18n.t(\"Peer review override available from date cannot be before parent override due date\") : I18n.t(\"Peer review available from date cannot be before assignment due date\")\n    elsif !parent_due_at && parent_unlock_at && child_unlock_at && parent_unlock_at > child_unlock_at\n      raise PeerReview::InvalidDatesError,\n            is_override ? I18n.t(\"Peer review override available from date cannot be before parent override available from date\") : I18n.t(\"Peer review available from date cannot be before assignment available from date\")\n    end\n\n    # Validate: parent unlock_at <= child due_at (for backward compatibility)\n    if parent_unlock_at && child_due_at && parent_unlock_at > child_due_at\n      raise PeerReview::InvalidDatesError,\n            is_override ? I18n.t(\"Peer review override due date cannot be before parent override available from date\") : I18n.t(\"Peer review due date cannot be before assignment available from date\")\n    end\n\n    # Validate: parent due_at <= child due_at\n    if parent_due_at && child_due_at && parent_due_at > child_due_at\n      raise PeerReview::InvalidDatesError,\n            is_override ? I18n.t(\"Peer review override due date cannot be before parent override due date\") : I18n.t(\"Peer review due date cannot be before assignment due date\")\n    end\n\n    # Validate: child due_at <= parent lock_at\n    if child_due_at && parent_lock_at && child_due_at > parent_lock_at\n      raise PeerReview::InvalidDatesError,\n            is_override ? I18n.t(\"Peer review override due date cannot be after parent override until date\") : I18n.t(\"Peer review due date cannot be after assignment until date\")\n    end\n\n    # Validate: child lock_at <= parent lock_at\n    if child_lock_at && parent_lock_at && child_lock_at > parent_lock_at\n      raise PeerReview::InvalidDatesError,","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/peer_review/validations.rb#L153-L189","documentation":"PeerReview::InvalidDatesError from the backward-compatibility check in validate_dates_within_parent_boundaries: the peer review's due date must not precede the parent's available-from date. Override wording: 'Peer review override due date cannot be before parent override available from date'; assignment wording: 'Peer review due date cannot be before assignment available from date'. This catches pathologically wrong payloads where the review closes before the parent even opens.","triggerScenarios":"Calling validate_peer_review_dates_against_parent_assignment or validate_override_dates_against_parent_override with parent unlock_at set and child due_at < parent unlock_at, e.g. parent available 2026-05-01 but peer review due 2026-04-15.","commonSituations":"Copy-paste of peer review dates from a previous term while parent dates moved forward; swapped due_at/unlock_at fields in an API client; overrides built programmatically with default epoch-ish dates.","solutions":["Set the peer review due_at to a timestamp >= parent (override) unlock_at","Or clear the peer review due_at if reviews need no deadline","Fix swapped fields: verify due_at/unlock_at were not transposed in the payload","Pre-validate: raise if parent.unlock_at && child.due_at && parent.unlock_at > child.due_at"],"exampleFix":"// before\npeer_review_dates = { unlock_at: '2026-05-10T00:00:00Z', due_at: '2026-04-15T00:00:00Z' } # parent unlock 2026-05-01\n// after\npeer_review_dates = { unlock_at: '2026-05-10T00:00:00Z', due_at: '2026-05-20T00:00:00Z' }","handlingStrategy":"validation","validationCode":"def review_due_after_parent_opens?(parent, pr_dates)\n  return true if parent.unlock_at.nil? || pr_dates[:due_at].nil?\n  parent.unlock_at <= Time.zone.parse(pr_dates[:due_at].to_s)\nend","typeGuard":null,"tryCatchPattern":"begin\n  validator.validate_peer_review_dates_against_parent_assignment(pr_dates, assignment)\nrescue PeerReview::InvalidDatesError => e\n  if e.message.include?('due date cannot be before assignment available')\n    pr_dates[:due_at] = [assignment.unlock_at, pr_dates[:due_at]].compact.max\n    retry\n  end\n  raise\nend","preventionTips":["Never let review due dates drift before the term start / parent availability","Detect swapped due_at/unlock_at fields with an assert unlock <= due pre-check","When copying dates between terms, shift all dates together","Keep a single source-of-truth date computation helper for integrations"],"tags":["peer-review","date-validation","parent-assignment","canvas-lms"],"backgroundTag":"invalid-argument-value","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}