{"record":{"id":"afdb050d467eb725","repo":"instructure/canvas-lms","slug":"peer-review-override-available-from-date-cannot-be-before-afdb05","errorCode":null,"errorMessage":"Peer review override available from date cannot be before parent override available from date","messagePattern":"Peer review override available from 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":165,"sourceCode":"  def validate_dates_within_parent_boundaries(child_dates:, parent_unlock_at:, parent_due_at:, parent_lock_at:, is_override: false)\n    child_unlock_at = parse_date(child_dates, :unlock_at)\n    child_due_at = parse_date(child_dates, :due_at)\n    child_lock_at = parse_date(child_dates, :lock_at)\n\n    # Validate: parent unlock_at <= parent due_at\n    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,","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/peer_review/validations.rb#L147-L183","documentation":"PeerReview::InvalidDatesError from the elsif branch of the child unlock_at check in validate_dates_within_parent_boundaries: when the parent has NO due_at, the peer review's available-from date must still not precede the parent's available-from date (override: 'Peer review override available from date cannot be before parent override available from date'; non-override wording at line 166). It guarantees peer reviews never open before the parent assignment itself is available.","triggerScenarios":"Calling validate_dates_within_parent_boundaries (via validate_peer_review_dates_against_parent_assignment or validate_override_dates_against_parent_override) with parent_due_at nil/absent, parent unlock_at set, and child unlock_at strictly earlier than parent unlock_at.","commonSituations":"Assignments with only an until date configured; overrides where due_at is not overridden (due_at_overridden false → nil) so validation falls back to unlock comparison; bulk-imported peer review schedules that start on day one of the course.","solutions":["Set the peer review unlock_at to a timestamp >= parent (override) unlock_at","Or give the parent assignment/override a due_at that is <= the desired peer review unlock_at so the primary branch applies","Or remove the peer review unlock_at entirely (nil) so it inherits availability","Pre-validate with the fallback logic: if parent.due_at.nil? && parent.unlock_at > child.unlock_at, fix child dates"],"exampleFix":"// before\nparent = assignment (no due_at, unlock_at: '2026-05-01T00:00:00Z')\npeer_review_dates = { unlock_at: '2026-04-20T00:00:00Z' }\n// after\npeer_review_dates = { unlock_at: '2026-05-01T00:00:00Z' }","handlingStrategy":"validation","validationCode":"def review_opens_after_parent_opens?(parent, pr_dates)\n  parent_due = parent.due_at\n  floor = parent_due || parent.unlock_at\n  return true if floor.nil? || pr_dates[:unlock_at].nil?\n  floor <= Time.zone.parse(pr_dates[:unlock_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?('before assignment available from')\n    pr_dates[:unlock_at] = assignment.unlock_at\n    retry\n  end\n  raise\nend","preventionTips":["Treat parent unlock_at as the earliest floor for any peer review date","For assignments without a due date, floor against unlock_at explicitly","Recompute peer review dates whenever parent availability changes","Test override scenarios where due_at_overridden is false so the unlock fallback applies"],"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"}