{"record":{"id":"449814a08790a28d","repo":"instructure/canvas-lms","slug":"not-found-select-provisional-grade","errorCode":null,"errorMessage":"not found","messagePattern":"not found","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/select_provisional_grade.rb","lineNumber":32,"sourceCode":"# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along\n# with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\nclass Mutations::SelectProvisionalGrade < Mutations::BaseMutation\n  argument :assignment_id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func(\"Assignment\")\n  argument :provisional_grade_id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func(\"ModeratedGrading::ProvisionalGrade\")\n\n  field :provisional_grade, Types::ProvisionalGradeType, null: true\n\n  def resolve(input:) # rubocop:disable GraphQL/UnusedArgument\n    assignment_id = input[:assignment_id]\n    provisional_grade_id = input[:provisional_grade_id]\n\n    assignment = Assignment.active.find(assignment_id)\n    raise GraphQL::ExecutionError, \"not found\" unless assignment.permits_moderation?(current_user)\n\n    provisional_grade = assignment.provisional_grades.find(provisional_grade_id)\n    student = provisional_grade.submission.user\n    selection = ModeratedGrading::Selection.find_or_create_by!(assignment:, student:) do |s|\n      s.selected_provisional_grade_id = provisional_grade.id\n    end\n    selection.update!(selected_provisional_grade_id: provisional_grade.id) unless selection.selected_provisional_grade_id == provisional_grade.id\n\n    selection.create_moderation_event(current_user)\n\n    { provisional_grade: }\n  rescue ActiveRecord::RecordNotFound\n    raise GraphQL::ExecutionError, \"not found\"\n  end\nend\n","sourceCodeStart":14,"sourceCodeEnd":48,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/select_provisional_grade.rb#L14-L48","documentation":"SelectProvisionalGrade raises \"not found\" when the active Assignment for assignment_id either does not exist or, more specifically, when `assignment.permits_moderation?(current_user)` returns false. Although the message says \"not found\", it is raised deliberately (not via rescue) as a GraphQL::ExecutionError to hide the existence of assignments the user may not moderate. The generic message is intentional so unauthorized users cannot probe assignment ids.","triggerScenarios":"Calling selectProvisionalGrade with an assignment_id that is not an active assignment, or where the current_user is not a final grader / moderator on the assignment (permits_moderation? false), or the assignment has no moderation set / grades already published.","commonSituations":"A teacher whose moderation rights were removed after the UI was rendered; calling the mutation before being added as a final grader; using a deleted or unpublished assignment id; a student or observer id token attempting moderation; per-course permission changes after grades were published.","solutions":["Confirm the current user is a final grader with 'Select final grade' moderation permission on that assignment before calling the mutation.","Verify the assignment_id refers to an active (non-deleted, non-soft-deleted) assignment in the same course/shard.","Check assignment state: moderated grading must be enabled and grades must not yet be published.","If the message should distinguish permission vs missing records, add an explicit exists check plus a permission check with distinct messages server-side."],"exampleFix":"// before (opaque failure)\nselectProvisionalGrade(input: { assignmentId: \"99\", provisionalGradeId: \"5\" })\n// after: verify moderation access first in a query\nquery {\n  assignment(id: \"99\") {\n    permissions { selectFinalGrade }\n    moderatedGrading { gradesPublished graderCount }\n  }\n}\n// then call the mutation as an authorized final grader","handlingStrategy":"validation","validationCode":"const perms = await client.query({ query: ASSIGNMENT_PERMS, variables: { id: assignmentId } });\nconst mg = perms?.data?.assignment?.moderatedGrading;\nif (!perms?.data?.assignment) throw new Error('assignment not found');\nif (mg?.gradesPublished) throw new Error('grades already published');\nif (!perms.data.assignment.permissions?.selectFinalGrade) throw new Error('no moderation permission');","typeGuard":"function canModerate(assignment, userId) { return !!assignment && assignment.moderatedGrading?.finalGraderId != null && assignment.permissions?.selectFinalGrade === true; }","tryCatchPattern":"try {\n  await client.mutate({ mutation: SELECT_PROVISIONAL_GRADE, variables });\n} catch (e) {\n  if (e.graphQLErrors?.some(g => g.message === 'not found')) {\n    // re-check moderation rights and assignment existence; refresh UI state\n  } else { throw e; }\n}","preventionTips":["Check permissions.selectFinalGrade before moderation mutations","Confirm grades are not yet published","Refresh assignment state after role or grader changes","Treat 'not found' here as possibly permission-related, not just missing data"],"tags":["graphql","moderated-grading","permission","not-found"],"backgroundTag":"resource-not-found","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"}