{"record":{"id":"7571c2ca32059cbb","repo":"instructure/canvas-lms","slug":"parent-override-due-date-cannot-be-before-parent-override","errorCode":null,"errorMessage":"Parent override due date cannot be before parent override available from date","messagePattern":"Parent 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":154,"sourceCode":"      parsed = Time.zone.parse(date_value)\n      if parsed.nil?\n        raise PeerReview::InvalidDatesError,\n              I18n.t(\"Invalid date format for %{field}: %{value}\", field:, value: date_value)\n      end\n      parsed\n    else\n      date_value\n    end\n  end\n\n  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\")","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/peer_review/validations.rb#L136-L172","documentation":"PeerReview::InvalidDatesError raised by validate_dates_within_parent_boundaries when the parent's own available-from date (unlock_at) is later than its due date. For overrides this reports 'Parent override due date cannot be before parent override available from date'; for the plain assignment path it reports the assignment wording. The parent's dates are checked first because child peer review dates are only meaningful relative to a coherent parent window.","triggerScenarios":"Calling validate_override_dates_against_parent_override with a parent_override whose due_at_overridden date precedes its unlock_at_overridden date, or validate_peer_review_dates_against_parent_assignment with a parent assignment whose unlock_at > due_at — even if the peer review dates themselves are fine.","commonSituations":"Imported assignment overrides with inconsistent dates; a parent assignment edited to start later without moving its due date; seed/test fixtures with arbitrary dates; APIs accepting parent override updates without their own validation.","solutions":["Fix the parent assignment/override: set unlock_at <= due_at before creating the peer review","Update the parent override record (AssignmentOverride unlock_at/due_at) so its window is ordered","If the parent legitimately has no due date, clear due_at (nil) so only unlock constraints apply","Pre-check parent dates in code: raise if parent.unlock_at && parent.due_at && parent.unlock_at > parent.due_at"],"exampleFix":"// before\nparent_override.update(unlock_at: '2026-05-10T00:00:00Z', due_at: '2026-05-01T00:00:00Z')\ncreate_peer_review_override(parent_override, ...)\n// after\nparent_override.update(unlock_at: '2026-05-01T00:00:00Z', due_at: '2026-05-10T00:00:00Z')\ncreate_peer_review_override(parent_override, ...)","handlingStrategy":"validation","validationCode":"def parent_window_ordered?(parent)\n  u, d = parent.unlock_at, parent.due_at\n  !(u && d && u > d)\nend\nraise 'parent dates inverted' unless parent_window_ordered?(parent_override)","typeGuard":null,"tryCatchPattern":"begin\n  create_peer_review_for(parent_override)\nrescue PeerReview::InvalidDatesError => e\n  if e.message.include?('Parent override')\n    fix_parent_dates!(parent_override)\n    retry\n  end\n  raise\nend","preventionTips":["Fix the parent assignment/override dates before configuring peer reviews","Run assignment-level date validation before adding overrides or sub assignments","Audit imported/seeded assignments for unlock_at > due_at","Never assume the parent is valid just because peer review dates look right"],"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"}