Skip to content

Add support for conductor assessments - #1367

Open
RichDom2185 wants to merge 5 commits into
masterfrom
conductor-assessments
Open

Add support for conductor assessments#1367
RichDom2185 wants to merge 5 commits into
masterfrom
conductor-assessments

Conversation

@RichDom2185

@RichDom2185 RichDom2185 commented Jul 28, 2026

Copy link
Copy Markdown
Member

Adding support for conductor (external runtime) assessments with XOR constraint for backwards compatibility


This is part 2 of 3 in a stack made with GitButler:

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e11d75d0-7166-4a2f-8834-4bd7b31c3d07

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for Conductor programming libraries, including language and evaluator settings.
    • Improved programming-language XML parsing and validation for legacy and Conductor formats.
    • Added automatic authentication token refresh handling.
    • Improved background grading and result processing reliability.
  • Bug Fixes

    • Improved assessment and story date validation when closing dates are missing.
    • Strengthened access checks for assessment scoring operations.
    • Improved error handling for invalid library configurations and grading requests.
  • Documentation

    • Updated API documentation to describe legacy and Conductor library formats.

Walkthrough

Changes

Core application changes

Layer / File(s) Summary
Conductor library contracts and XML processing
lib/cadet/assessments/library.ex, lib/cadet/jobs/xml_parser.ex, lib/cadet_web/helpers/assessments_helpers.ex, lib/cadet_web/controllers/assessments_controller.ex, test/cadet/assessments/library_test.exs, test/cadet/updater/xml_parser_test.exs
Added legacy and Conductor library formats with format-specific validation, XML parsing, rendering, Swagger schemas, fixtures, and tests.
Oban autograding workflow
config/config.exs, lib/cadet/jobs/autograder/*, test/cadet/jobs/autograder/*, mix.exs, priv/repo/migrations/20260715000000_update_oban_to_v14.exs
Migrated autograding workers from Que to Oban. Added dedicated queues, ID-based jobs, result persistence, failure handling, and Oban test assertions.
Native DateTime migration
lib/cadet/assessments/*, lib/cadet/jobs/*, lib/cadet/stories/*, lib/cadet_web/*, test/cadet/*, test/cadet_web/*, test/factories/*
Replaced Timex time operations with UTC DateTime, Date, and integer-second APIs across application code and tests.
Runtime and web integration
lib/cadet/application.ex, lib/cadet/auth/guardian.ex, lib/cadet_web.ex, lib/cadet_web/endpoint.ex, lib/cadet_web/plug/cors.ex, lib/cadet_web/admin_controllers/*, lib/cadet/logger/cloudwatch_logger.ex
Updated supervision specifications, token refresh handling, Phoenix setup, CORS configuration, controller authorization, and logger fallback callbacks.
Maintenance and analysis artifacts
.credo.exs, .mcp.json, credo-branch.txt, credo-original.txt, lib/cadet/accounts/teams.ex
Updated Credo configuration and reports, added MCP server configuration, and removed unused calculations and private-function documentation.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: adding support for conductor assessments.
Description check ✅ Passed The description directly explains conductor assessment support and backward compatibility through an XOR constraint.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coveralls

coveralls commented Jul 28, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 87.393% (-0.07%) from 87.459% — conductor-assessments into master

@RichDom2185
RichDom2185 force-pushed the conductor-assessments branch from dc5f5db to 0e8a4da Compare July 28, 2026 02:17
@RichDom2185 RichDom2185 self-assigned this Jul 28, 2026
@RichDom2185
RichDom2185 force-pushed the conductor-assessments branch from 3394f2a to 2e5e295 Compare August 1, 2026 04:15
Base automatically changed from deps-big-bang-2026 to master August 1, 2026 04:43
@RichDom2185
RichDom2185 marked this pull request as ready for review August 1, 2026 04:44
@RichDom2185
RichDom2185 force-pushed the conductor-assessments branch from 2e5e295 to 3999d4b Compare August 1, 2026 04:48
@RichDom2185 RichDom2185 changed the title conductor-assessments Add support for conductor assessments Aug 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (7)
.credo.exs (1)

97-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the global complexity limit at 11 unless a current violation requires 16.

Line 97 raises the limit for every analyzed module. The supplied credo-original.txt and credo-branch.txt reports contain no CyclomaticComplexity finding. Run Credo with the limit set to 11 on this branch. If no violation appears, revert this change. Otherwise, refactor the specific function or document a scoped exception.

Suggested default
-        {Credo.Check.Refactor.CyclomaticComplexity, max_complexity: 16},
+        {Credo.Check.Refactor.CyclomaticComplexity, max_complexity: 11},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.credo.exs at line 97, Revert the global max_complexity change in the Credo
configuration to 11 unless running Credo at that limit reveals a current
violation. If a violation exists, refactor the identified function or apply a
narrowly scoped exception instead of raising the limit for all modules.
test/support/xml_generator.ex (1)

225-233: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Legacy library_attrs drops variant and exectime.

programminglanguage/2 and graderprogramminglanguage/2 now permit variant and exectime attributes, but library_attrs/1 for the legacy (non-conductor) clause only returns %{interpreter: library.chapter}. Legacy Library structs carry variant and exec_time_ms. As written, no test built through process_library/2 can generate legacy XML with variant/exectime attributes, even though the tag now supports them.

Add variant and exectime to the legacy library_attrs/1 clause so tests can exercise variant/exectime round-tripping through the generator.

♻️ Proposed fix
   defp library_attrs(library) do
-    %{interpreter: library.chapter}
+    %{interpreter: library.chapter, variant: library[:variant], exectime: library[:exec_time_ms]}
+    |> Map.reject(fn {_k, v} -> is_nil(v) end)
   end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/support/xml_generator.ex` around lines 225 - 233, Update the legacy
`library_attrs/1` clause to include the `variant` and `exectime` XML attributes,
mapping them from the legacy `Library` fields `variant` and `exec_time_ms` while
retaining the existing `interpreter: library.chapter` attribute.
lib/cadet/jobs/xml_parser.ex (1)

296-331: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Allow mixed library/grading_library formats if supported.

parse_programming_language/1 can produce different :format values for the two fields, and LambdaWorker routes question.grading_library by its own :format. Keep this parsing behavior if mixed legacy/conductor values are valid, or add an explicit validation rule that rejects it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet/jobs/xml_parser.ex` around lines 296 - 331, Update
process_question_library/3 to explicitly handle mixed formats between the parsed
library and grading_library values: preserve both results when mixed
legacy/conductor formats are supported, or add validation that returns an error
when their :format values differ. Ensure the behavior aligns with LambdaWorker
routing grading_library by its own format.
lib/cadet/jobs/autograder/result_store_worker.ex (1)

6-8: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider retries for the result-persistence queue.

max_attempts: 1 discards the job after a single transient database error, so the autograding result is lost and the answer keeps autograding_status: :processing. The write path is idempotent per answer_id, so a small max_attempts with backoff is safe and recovers from short database outages.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet/jobs/autograder/result_store_worker.ex` around lines 6 - 8, Update
the Oban.Worker configuration for the result-persistence worker to use a small
retry count instead of max_attempts: 1, preserving the idempotent answer_id
write path and enabling Oban’s backoff to recover from transient database
errors.
test/cadet_web/controllers/stories_controller_test.exs (1)

13-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the leftover use Timex.

This file no longer calls any Timex function after the migration. The use Timex directive at Line 3 is now dead setup and keeps this test coupled to Timex.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/cadet_web/controllers/stories_controller_test.exs` around lines 13 - 14,
Remove the unused use Timex directive from the test module, leaving the
DateTime-based setup and remaining test configuration unchanged.
lib/cadet/jobs/autograder/lambda_worker.ex (1)

122-124: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move get_arg/3 to a shared helper.

Cadet.Autograder.ResultStoreWorker defines the identical private get_arg/3 at lines 80-82 of lib/cadet/jobs/autograder/result_store_worker.ex. That module already imports Cadet.SharedHelper. Put one implementation there and import it in both workers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet/jobs/autograder/lambda_worker.ex` around lines 122 - 124, Move the
duplicated get_arg/3 implementation from the worker modules into
Cadet.SharedHelper, then import or otherwise expose that shared helper in both
Cadet.Autograder.LambdaWorker and Cadet.Autograder.ResultStoreWorker. Remove
each worker’s private definition while preserving the existing key and default
lookup behavior.
lib/cadet/jobs/autograder/utilities.ex (1)

16-38: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Enqueue the job in the same transaction as the status update.

Repo.update!/1 commits autograding_status: :processing before Oban.insert/1 runs. If the insert fails, or the process stops between the two operations, the answer stays :processing and is never regraded, because grade_submission_question_answer_lists/5 only regrades answers with status :none or :failed. Wrap both operations in one Ecto.Multi and use Oban.insert/3 with the multi so the status change rolls back with a failed enqueue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet/jobs/autograder/utilities.ex` around lines 16 - 38, Update
dispatch_programming_answer/3 to execute the Answer.autograding_changeset update
and Oban enqueue within a single Ecto.Multi transaction, replacing the
standalone Repo.update!/1 and Oban.insert/1 calls. Use Oban.insert/3 with the
multi so any enqueue failure rolls back the :processing status update, while
preserving the existing job arguments and overwrite behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.mcp.json:
- Line 7: Remove the hardcoded CONTEXT7_API_KEY value from .mcp.json, revoke and
rotate the exposed credential, purge it from Git history, and configure the MCP
client to obtain the key through its supported secret mechanism instead.

In `@lib/cadet_web/admin_controllers/admin_teams_controller.ex`:
- Around line 93-94: Update the course-scoped delete clause in
AdminTeamsController.delete to load the team through its assessment and validate
that assessment.course_id matches the requested course_id before deletion.
Return the existing 404/403 response when the team is missing or belongs to
another course, and only delegate to the team-id deletion path after this
authorization check.

In `@lib/cadet_web/controllers/generate_ai_comments.ex`:
- Line 53: Update check_llm_grading_parameters/3 to validate that the decrypted
LLM API key is non-empty before allowing the grading request to proceed. Return
the existing configuration-error result for an empty key, while preserving the
current behavior for valid keys and other parameter checks.

In `@lib/cadet_web/plug/cors.ex`:
- Around line 42-44: Update the CORS configuration lookup in the endpoint
settings flow to require an explicit :cors_endpoints value instead of defaulting
to "*". Reject "*" when credentialed CORS is enabled, while preserving valid
explicit origin-list handling and failing closed for missing or invalid
configuration.

In `@lib/cadet/jobs/autograder/lambda_worker.ex`:
- Around line 75-79: Update enqueue_result_store/1 to propagate the result of
Oban.insert/1 instead of discarding it, and make run_with_models/1 return that
result rather than unconditionally returning :ok. Apply the same result handling
to the enqueue_result_store/1 call in handle_failure/4 so insertion errors
propagate in both success and failure paths.

In `@lib/cadet/jobs/autograder/result_store_worker.ex`:
- Around line 67-78: Add fallback handling in normalize_status/1 for unknown
string or other status values, mapping them to a safe default status instead of
raising and leaving the answer in :processing; also add a non-map clause in
normalize_result/1 so nil results are normalized without crashing. Preserve the
existing mappings for "success", "failed", and atom statuses.

In `@lib/cadet/jobs/xml_parser.ex`:
- Around line 63-81: Update extract_changeset_error_message to traverse nested
embed errors via Ecto.Changeset.traverse_errors/2, including validation messages
from embedded Library changesets for :library and :grading_library instead of
reducing them to the generic "embed invalid" text. Preserve the existing
formatting and stringification behavior for top-level errors.

In `@test/cadet/updater/xml_parser_test.exs`:
- Around line 280-310: Update the “Conductor programming language” tests to
cover distinct XML paths: construct the TASK-level fixture using the XML
generator’s library: and grading_library: options, and give the per-PROBLEM
override fixture a different library. After XMLParser.parse_xml, assert the
persisted task-level and problem-level library values so each path is verified
independently.

---

Nitpick comments:
In @.credo.exs:
- Line 97: Revert the global max_complexity change in the Credo configuration to
11 unless running Credo at that limit reveals a current violation. If a
violation exists, refactor the identified function or apply a narrowly scoped
exception instead of raising the limit for all modules.

In `@lib/cadet/jobs/autograder/lambda_worker.ex`:
- Around line 122-124: Move the duplicated get_arg/3 implementation from the
worker modules into Cadet.SharedHelper, then import or otherwise expose that
shared helper in both Cadet.Autograder.LambdaWorker and
Cadet.Autograder.ResultStoreWorker. Remove each worker’s private definition
while preserving the existing key and default lookup behavior.

In `@lib/cadet/jobs/autograder/result_store_worker.ex`:
- Around line 6-8: Update the Oban.Worker configuration for the
result-persistence worker to use a small retry count instead of max_attempts: 1,
preserving the idempotent answer_id write path and enabling Oban’s backoff to
recover from transient database errors.

In `@lib/cadet/jobs/autograder/utilities.ex`:
- Around line 16-38: Update dispatch_programming_answer/3 to execute the
Answer.autograding_changeset update and Oban enqueue within a single Ecto.Multi
transaction, replacing the standalone Repo.update!/1 and Oban.insert/1 calls.
Use Oban.insert/3 with the multi so any enqueue failure rolls back the
:processing status update, while preserving the existing job arguments and
overwrite behavior.

In `@lib/cadet/jobs/xml_parser.ex`:
- Around line 296-331: Update process_question_library/3 to explicitly handle
mixed formats between the parsed library and grading_library values: preserve
both results when mixed legacy/conductor formats are supported, or add
validation that returns an error when their :format values differ. Ensure the
behavior aligns with LambdaWorker routing grading_library by its own format.

In `@test/cadet_web/controllers/stories_controller_test.exs`:
- Around line 13-14: Remove the unused use Timex directive from the test module,
leaving the DateTime-based setup and remaining test configuration unchanged.

In `@test/support/xml_generator.ex`:
- Around line 225-233: Update the legacy `library_attrs/1` clause to include the
`variant` and `exectime` XML attributes, mapping them from the legacy `Library`
fields `variant` and `exec_time_ms` while retaining the existing `interpreter:
library.chapter` attribute.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a61d9f9-4b39-498d-bd1a-14441c0a266d

📥 Commits

Reviewing files that changed from the base of the PR and between ad975fd and 2e5e295.

⛔ Files ignored due to path filters (1)
  • mix.lock is excluded by !**/*.lock
📒 Files selected for processing (60)
  • .credo.exs
  • .mcp.json
  • config/config.exs
  • credo-branch.txt
  • credo-original.txt
  • lib/cadet/accounts/teams.ex
  • lib/cadet/application.ex
  • lib/cadet/assessments/assessment.ex
  • lib/cadet/assessments/assessments.ex
  • lib/cadet/assessments/library.ex
  • lib/cadet/assessments/version_manager.ex
  • lib/cadet/auth/guardian.ex
  • lib/cadet/auth/providers/openid/nus_entra_id_claim_extractor.ex
  • lib/cadet/chatbot/llm_conversations.ex
  • lib/cadet/code_exchange.ex
  • lib/cadet/helpers/model_helper.ex
  • lib/cadet/jobs/autograder/grading_job.ex
  • lib/cadet/jobs/autograder/lambda_worker.ex
  • lib/cadet/jobs/autograder/result_store_worker.ex
  • lib/cadet/jobs/autograder/utilities.ex
  • lib/cadet/jobs/log.ex
  • lib/cadet/jobs/xml_parser.ex
  • lib/cadet/logger/cloudwatch_logger.ex
  • lib/cadet/stories/stories.ex
  • lib/cadet/stories/story.ex
  • lib/cadet_web.ex
  • lib/cadet_web/admin_controllers/admin_assessments_controller.ex
  • lib/cadet_web/admin_controllers/admin_teams_controller.ex
  • lib/cadet_web/admin_views/admin_grading_view.ex
  • lib/cadet_web/controllers/assessments_controller.ex
  • lib/cadet_web/controllers/auth_controller.ex
  • lib/cadet_web/controllers/generate_ai_comments.ex
  • lib/cadet_web/endpoint.ex
  • lib/cadet_web/helpers/ai_comments_helpers.ex
  • lib/cadet_web/helpers/assessments_helpers.ex
  • lib/cadet_web/helpers/view_helper.ex
  • lib/cadet_web/plug/cors.ex
  • mix.exs
  • priv/repo/migrations/20230214140555_create_notification_preferences.exs
  • priv/repo/migrations/20260715000000_update_oban_to_v14.exs
  • test/cadet/assessments/assessment_test.exs
  • test/cadet/assessments/assessments_test.exs
  • test/cadet/assessments/library_test.exs
  • test/cadet/jobs/autograder/grading_job_test.exs
  • test/cadet/jobs/autograder/lambda_worker_test.exs
  • test/cadet/jobs/autograder/result_store_worker_test.exs
  • test/cadet/jobs/autograder/utilities_test.exs
  • test/cadet/jobs/log_test.exs
  • test/cadet/stories/stories_test.exs
  • test/cadet/updater/xml_parser_test.exs
  • test/cadet_web/admin_controllers/admin_assessments_controller_test.exs
  • test/cadet_web/admin_controllers/admin_grading_controller_test.exs
  • test/cadet_web/admin_controllers/admin_stories_controller_test.exs
  • test/cadet_web/controllers/answer_controller_test.exs
  • test/cadet_web/controllers/assessments_controller_test.exs
  • test/cadet_web/controllers/stories_controller_test.exs
  • test/factories/assessments/assessment_factory.ex
  • test/factories/assessments/library_factory.ex
  • test/factories/stories/story_factory.ex
  • test/support/xml_generator.ex
💤 Files with no reviewable changes (1)
  • lib/cadet/assessments/version_manager.ex

Comment thread .mcp.json Outdated
"type": "http",
"url": "https://mcp.context7.com/mcp",
"headers": {
"CONTEXT7_API_KEY": "ctx7sk-9324f560-42a4-4383-a942-ac6e0cb12d45"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Remove the committed Context7 API key.

Line 7 stores a credential in the repository. Revoke and rotate this key before merge, purge it from Git history, and load it through the MCP client's supported secret mechanism instead.

🧰 Tools
🪛 Betterleaks (1.7.0)

[high] 7-7: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.mcp.json at line 7, Remove the hardcoded CONTEXT7_API_KEY value from
.mcp.json, revoke and rotate the exposed credential, purge it from Git history,
and configure the MCP client to obtain the key through its supported secret
mechanism instead.

Source: Linters/SAST tools

Comment on lines 63 to +81
defp extract_changeset_error_message(errors_list) do
errors_list
|> Enum.map(fn {field, {error, _}} -> "#{to_string(field)} #{error}" end)
|> List.foldr("", fn x, acc -> "#{acc <> x} " end)
|> Enum.map_join(
" ",
fn {field, messages} ->
formatted =
messages
|> List.wrap()
|> Enum.map_join(" ", &stringify_error_message/1)

"#{to_string(field)} #{formatted}"
end
)
end

defp stringify_error_message(msg) when is_binary(msg), do: msg
defp stringify_error_message({msg, _opts}) when is_binary(msg), do: msg
defp stringify_error_message(%Ecto.Changeset{}), do: "embed invalid"
defp stringify_error_message(other), do: inspect(other)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check how Library embed errors propagate into parent changesets.
rg -n 'cast_embed\(:library' lib/ 
rg -n 'traverse_errors' lib/ -A3 -B3
ast-grep run --pattern 'def changeset($_, $_) { $$$ }' --lang elixir lib/cadet/assessments/library.ex 2>/dev/null
cat -n lib/cadet/assessments/library.ex 2>/dev/null | head -80

Repository: source-academy/backend

Length of output: 3822


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Changeset definitions"
for f in lib/cadet/assessments/question.ex lib/cadet/assessments/assessment.ex; do
  if [ -f "$f" ]; then
    echo "--- $f line count ---"
    wc -l "$f"
    echo "--- changeset occurrences ---"
    rg -n 'def changeset|cast_embed|errors|from_schema|validate_required|add_error' "$f" -A8 -B4
  fi
done

echo "## XML parser relevant functions"
cat -n lib/cadet/jobs/xml_parser.ex | sed -n '1,70p;85,155p;330,375p'

Repository: source-academy/backend

Length of output: 10994


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Embedded validation helpers"
for f in lib/cadet/assessments/question.ex lib/cadet/assessments/library.ex lib/cadet/assessments/external_library.ex; do
  echo "--- $f line count ---"
  wc -l "$f"
  echo "--- relevant functions ---"
  rg -n 'validate_arbitrary_embedded_struct_by_type|validate_programming_language_element|validate_conductor_no_external|validate_conductor_no_legacy_fields|to_changeset|changeset|cast_embed' "$f" -A12 -B4
done

echo "## Changeset insertion path"
if [ -f lib/cadet/assessments.ex ]; then
  rg -n 'insert_or_update_assessments_and_questions|question_changeset|Question\.changeset|changeset\(.*Question|on_replace|cast_embed\(:library|cast_embed\(:grading_library' lib/cadet/assessments.ex -A12 -B4
fi

Repository: source-academy/backend

Length of output: 6316


Traverse Library embed errors when logging changeset failures.

Question.changeset/2 casts :library and :grading_library as embeds, but Ecto.Changeset.errors/1 only reports top-level errors. Nested Library validation failures are not added as top-level {field, messages} tuples, so this path can log a generic embed error instead of the specific Library validation reason. Use traverse_errors/2 here, or have Library errors add a top-level error on the parent changeset.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet/jobs/xml_parser.ex` around lines 63 - 81, Update
extract_changeset_error_message to traverse nested embed errors via
Ecto.Changeset.traverse_errors/2, including validation messages from embedded
Library changesets for :library and :grading_library instead of reducing them to
the generic "embed invalid" text. Preserve the existing formatting and
stringification behavior for top-level errors.

Comment on lines +280 to +310
describe "Conductor programming language" do
test "happy path at TASK-level applies to all problems", %{
course: course,
assessments_with_config: assessments_with_config
} do
conductor =
build(:programming_question,
library: build(:conductor_library),
grading_library: build(:conductor_library)
)

for {assessment, assessment_config} <- assessments_with_config do
xml = XMLGenerator.generate_xml_for(assessment, [conductor])
assert :ok == XMLParser.parse_xml(xml, course.id, assessment_config.id)
end
end

test "happy path per-PROBLEM override", %{
course: course,
assessments_with_config: assessments_with_config
} do
conductor =
build(:programming_question,
library: build(:conductor_library),
grading_library: build(:conductor_library)
)

for {assessment, assessment_config} <- assessments_with_config do
xml = XMLGenerator.generate_xml_for(assessment, [conductor])
assert :ok == XMLParser.parse_xml(xml, course.id, assessment_config.id)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test the task-level and override XML paths.

Line 292 emits the library from question.library. It does not use the generator library: or grading_library: options for TASK-level elements. Lines 291-310 therefore test the same per-PROBLEM path.

Create a TASK-level fixture through library: and grading_library:. Add a distinct per-PROBLEM library in the override test. Assert the persisted library values after parsing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/cadet/updater/xml_parser_test.exs` around lines 280 - 310, Update the
“Conductor programming language” tests to cover distinct XML paths: construct
the TASK-level fixture using the XML generator’s library: and grading_library:
options, and give the per-PROBLEM override fixture a different library. After
XMLParser.parse_xml, assert the persisted task-level and problem-level library
values so each path is verified independently.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 8

🧹 Nitpick comments (7)
.credo.exs (1)

97-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the global complexity limit at 11 unless a current violation requires 16.

Line 97 raises the limit for every analyzed module. The supplied credo-original.txt and credo-branch.txt reports contain no CyclomaticComplexity finding. Run Credo with the limit set to 11 on this branch. If no violation appears, revert this change. Otherwise, refactor the specific function or document a scoped exception.

Suggested default
-        {Credo.Check.Refactor.CyclomaticComplexity, max_complexity: 16},
+        {Credo.Check.Refactor.CyclomaticComplexity, max_complexity: 11},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.credo.exs at line 97, Revert the global max_complexity change in the Credo
configuration to 11 unless running Credo at that limit reveals a current
violation. If a violation exists, refactor the identified function or apply a
narrowly scoped exception instead of raising the limit for all modules.
test/support/xml_generator.ex (1)

225-233: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Legacy library_attrs drops variant and exectime.

programminglanguage/2 and graderprogramminglanguage/2 now permit variant and exectime attributes, but library_attrs/1 for the legacy (non-conductor) clause only returns %{interpreter: library.chapter}. Legacy Library structs carry variant and exec_time_ms. As written, no test built through process_library/2 can generate legacy XML with variant/exectime attributes, even though the tag now supports them.

Add variant and exectime to the legacy library_attrs/1 clause so tests can exercise variant/exectime round-tripping through the generator.

♻️ Proposed fix
   defp library_attrs(library) do
-    %{interpreter: library.chapter}
+    %{interpreter: library.chapter, variant: library[:variant], exectime: library[:exec_time_ms]}
+    |> Map.reject(fn {_k, v} -> is_nil(v) end)
   end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/support/xml_generator.ex` around lines 225 - 233, Update the legacy
`library_attrs/1` clause to include the `variant` and `exectime` XML attributes,
mapping them from the legacy `Library` fields `variant` and `exec_time_ms` while
retaining the existing `interpreter: library.chapter` attribute.
lib/cadet/jobs/xml_parser.ex (1)

296-331: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Allow mixed library/grading_library formats if supported.

parse_programming_language/1 can produce different :format values for the two fields, and LambdaWorker routes question.grading_library by its own :format. Keep this parsing behavior if mixed legacy/conductor values are valid, or add an explicit validation rule that rejects it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet/jobs/xml_parser.ex` around lines 296 - 331, Update
process_question_library/3 to explicitly handle mixed formats between the parsed
library and grading_library values: preserve both results when mixed
legacy/conductor formats are supported, or add validation that returns an error
when their :format values differ. Ensure the behavior aligns with LambdaWorker
routing grading_library by its own format.
lib/cadet/jobs/autograder/result_store_worker.ex (1)

6-8: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider retries for the result-persistence queue.

max_attempts: 1 discards the job after a single transient database error, so the autograding result is lost and the answer keeps autograding_status: :processing. The write path is idempotent per answer_id, so a small max_attempts with backoff is safe and recovers from short database outages.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet/jobs/autograder/result_store_worker.ex` around lines 6 - 8, Update
the Oban.Worker configuration for the result-persistence worker to use a small
retry count instead of max_attempts: 1, preserving the idempotent answer_id
write path and enabling Oban’s backoff to recover from transient database
errors.
test/cadet_web/controllers/stories_controller_test.exs (1)

13-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the leftover use Timex.

This file no longer calls any Timex function after the migration. The use Timex directive at Line 3 is now dead setup and keeps this test coupled to Timex.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/cadet_web/controllers/stories_controller_test.exs` around lines 13 - 14,
Remove the unused use Timex directive from the test module, leaving the
DateTime-based setup and remaining test configuration unchanged.
lib/cadet/jobs/autograder/lambda_worker.ex (1)

122-124: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move get_arg/3 to a shared helper.

Cadet.Autograder.ResultStoreWorker defines the identical private get_arg/3 at lines 80-82 of lib/cadet/jobs/autograder/result_store_worker.ex. That module already imports Cadet.SharedHelper. Put one implementation there and import it in both workers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet/jobs/autograder/lambda_worker.ex` around lines 122 - 124, Move the
duplicated get_arg/3 implementation from the worker modules into
Cadet.SharedHelper, then import or otherwise expose that shared helper in both
Cadet.Autograder.LambdaWorker and Cadet.Autograder.ResultStoreWorker. Remove
each worker’s private definition while preserving the existing key and default
lookup behavior.
lib/cadet/jobs/autograder/utilities.ex (1)

16-38: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Enqueue the job in the same transaction as the status update.

Repo.update!/1 commits autograding_status: :processing before Oban.insert/1 runs. If the insert fails, or the process stops between the two operations, the answer stays :processing and is never regraded, because grade_submission_question_answer_lists/5 only regrades answers with status :none or :failed. Wrap both operations in one Ecto.Multi and use Oban.insert/3 with the multi so the status change rolls back with a failed enqueue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet/jobs/autograder/utilities.ex` around lines 16 - 38, Update
dispatch_programming_answer/3 to execute the Answer.autograding_changeset update
and Oban enqueue within a single Ecto.Multi transaction, replacing the
standalone Repo.update!/1 and Oban.insert/1 calls. Use Oban.insert/3 with the
multi so any enqueue failure rolls back the :processing status update, while
preserving the existing job arguments and overwrite behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.mcp.json:
- Line 7: Remove the hardcoded CONTEXT7_API_KEY value from .mcp.json, revoke and
rotate the exposed credential, purge it from Git history, and configure the MCP
client to obtain the key through its supported secret mechanism instead.

In `@lib/cadet_web/admin_controllers/admin_teams_controller.ex`:
- Around line 93-94: Update the course-scoped delete clause in
AdminTeamsController.delete to load the team through its assessment and validate
that assessment.course_id matches the requested course_id before deletion.
Return the existing 404/403 response when the team is missing or belongs to
another course, and only delegate to the team-id deletion path after this
authorization check.

In `@lib/cadet_web/controllers/generate_ai_comments.ex`:
- Line 53: Update check_llm_grading_parameters/3 to validate that the decrypted
LLM API key is non-empty before allowing the grading request to proceed. Return
the existing configuration-error result for an empty key, while preserving the
current behavior for valid keys and other parameter checks.

In `@lib/cadet_web/plug/cors.ex`:
- Around line 42-44: Update the CORS configuration lookup in the endpoint
settings flow to require an explicit :cors_endpoints value instead of defaulting
to "*". Reject "*" when credentialed CORS is enabled, while preserving valid
explicit origin-list handling and failing closed for missing or invalid
configuration.

In `@lib/cadet/jobs/autograder/lambda_worker.ex`:
- Around line 75-79: Update enqueue_result_store/1 to propagate the result of
Oban.insert/1 instead of discarding it, and make run_with_models/1 return that
result rather than unconditionally returning :ok. Apply the same result handling
to the enqueue_result_store/1 call in handle_failure/4 so insertion errors
propagate in both success and failure paths.

In `@lib/cadet/jobs/autograder/result_store_worker.ex`:
- Around line 67-78: Add fallback handling in normalize_status/1 for unknown
string or other status values, mapping them to a safe default status instead of
raising and leaving the answer in :processing; also add a non-map clause in
normalize_result/1 so nil results are normalized without crashing. Preserve the
existing mappings for "success", "failed", and atom statuses.

In `@lib/cadet/jobs/xml_parser.ex`:
- Around line 63-81: Update extract_changeset_error_message to traverse nested
embed errors via Ecto.Changeset.traverse_errors/2, including validation messages
from embedded Library changesets for :library and :grading_library instead of
reducing them to the generic "embed invalid" text. Preserve the existing
formatting and stringification behavior for top-level errors.

In `@test/cadet/updater/xml_parser_test.exs`:
- Around line 280-310: Update the “Conductor programming language” tests to
cover distinct XML paths: construct the TASK-level fixture using the XML
generator’s library: and grading_library: options, and give the per-PROBLEM
override fixture a different library. After XMLParser.parse_xml, assert the
persisted task-level and problem-level library values so each path is verified
independently.

---

Nitpick comments:
In @.credo.exs:
- Line 97: Revert the global max_complexity change in the Credo configuration to
11 unless running Credo at that limit reveals a current violation. If a
violation exists, refactor the identified function or apply a narrowly scoped
exception instead of raising the limit for all modules.

In `@lib/cadet/jobs/autograder/lambda_worker.ex`:
- Around line 122-124: Move the duplicated get_arg/3 implementation from the
worker modules into Cadet.SharedHelper, then import or otherwise expose that
shared helper in both Cadet.Autograder.LambdaWorker and
Cadet.Autograder.ResultStoreWorker. Remove each worker’s private definition
while preserving the existing key and default lookup behavior.

In `@lib/cadet/jobs/autograder/result_store_worker.ex`:
- Around line 6-8: Update the Oban.Worker configuration for the
result-persistence worker to use a small retry count instead of max_attempts: 1,
preserving the idempotent answer_id write path and enabling Oban’s backoff to
recover from transient database errors.

In `@lib/cadet/jobs/autograder/utilities.ex`:
- Around line 16-38: Update dispatch_programming_answer/3 to execute the
Answer.autograding_changeset update and Oban enqueue within a single Ecto.Multi
transaction, replacing the standalone Repo.update!/1 and Oban.insert/1 calls.
Use Oban.insert/3 with the multi so any enqueue failure rolls back the
:processing status update, while preserving the existing job arguments and
overwrite behavior.

In `@lib/cadet/jobs/xml_parser.ex`:
- Around line 296-331: Update process_question_library/3 to explicitly handle
mixed formats between the parsed library and grading_library values: preserve
both results when mixed legacy/conductor formats are supported, or add
validation that returns an error when their :format values differ. Ensure the
behavior aligns with LambdaWorker routing grading_library by its own format.

In `@test/cadet_web/controllers/stories_controller_test.exs`:
- Around line 13-14: Remove the unused use Timex directive from the test module,
leaving the DateTime-based setup and remaining test configuration unchanged.

In `@test/support/xml_generator.ex`:
- Around line 225-233: Update the legacy `library_attrs/1` clause to include the
`variant` and `exectime` XML attributes, mapping them from the legacy `Library`
fields `variant` and `exec_time_ms` while retaining the existing `interpreter:
library.chapter` attribute.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a61d9f9-4b39-498d-bd1a-14441c0a266d

📥 Commits

Reviewing files that changed from the base of the PR and between ad975fd and 2e5e295.

⛔ Files ignored due to path filters (1)
  • mix.lock is excluded by !**/*.lock
📒 Files selected for processing (60)
  • .credo.exs
  • .mcp.json
  • config/config.exs
  • credo-branch.txt
  • credo-original.txt
  • lib/cadet/accounts/teams.ex
  • lib/cadet/application.ex
  • lib/cadet/assessments/assessment.ex
  • lib/cadet/assessments/assessments.ex
  • lib/cadet/assessments/library.ex
  • lib/cadet/assessments/version_manager.ex
  • lib/cadet/auth/guardian.ex
  • lib/cadet/auth/providers/openid/nus_entra_id_claim_extractor.ex
  • lib/cadet/chatbot/llm_conversations.ex
  • lib/cadet/code_exchange.ex
  • lib/cadet/helpers/model_helper.ex
  • lib/cadet/jobs/autograder/grading_job.ex
  • lib/cadet/jobs/autograder/lambda_worker.ex
  • lib/cadet/jobs/autograder/result_store_worker.ex
  • lib/cadet/jobs/autograder/utilities.ex
  • lib/cadet/jobs/log.ex
  • lib/cadet/jobs/xml_parser.ex
  • lib/cadet/logger/cloudwatch_logger.ex
  • lib/cadet/stories/stories.ex
  • lib/cadet/stories/story.ex
  • lib/cadet_web.ex
  • lib/cadet_web/admin_controllers/admin_assessments_controller.ex
  • lib/cadet_web/admin_controllers/admin_teams_controller.ex
  • lib/cadet_web/admin_views/admin_grading_view.ex
  • lib/cadet_web/controllers/assessments_controller.ex
  • lib/cadet_web/controllers/auth_controller.ex
  • lib/cadet_web/controllers/generate_ai_comments.ex
  • lib/cadet_web/endpoint.ex
  • lib/cadet_web/helpers/ai_comments_helpers.ex
  • lib/cadet_web/helpers/assessments_helpers.ex
  • lib/cadet_web/helpers/view_helper.ex
  • lib/cadet_web/plug/cors.ex
  • mix.exs
  • priv/repo/migrations/20230214140555_create_notification_preferences.exs
  • priv/repo/migrations/20260715000000_update_oban_to_v14.exs
  • test/cadet/assessments/assessment_test.exs
  • test/cadet/assessments/assessments_test.exs
  • test/cadet/assessments/library_test.exs
  • test/cadet/jobs/autograder/grading_job_test.exs
  • test/cadet/jobs/autograder/lambda_worker_test.exs
  • test/cadet/jobs/autograder/result_store_worker_test.exs
  • test/cadet/jobs/autograder/utilities_test.exs
  • test/cadet/jobs/log_test.exs
  • test/cadet/stories/stories_test.exs
  • test/cadet/updater/xml_parser_test.exs
  • test/cadet_web/admin_controllers/admin_assessments_controller_test.exs
  • test/cadet_web/admin_controllers/admin_grading_controller_test.exs
  • test/cadet_web/admin_controllers/admin_stories_controller_test.exs
  • test/cadet_web/controllers/answer_controller_test.exs
  • test/cadet_web/controllers/assessments_controller_test.exs
  • test/cadet_web/controllers/stories_controller_test.exs
  • test/factories/assessments/assessment_factory.ex
  • test/factories/assessments/library_factory.ex
  • test/factories/stories/story_factory.ex
  • test/support/xml_generator.ex
💤 Files with no reviewable changes (1)
  • lib/cadet/assessments/version_manager.ex
🛑 Comments failed to post (5)
lib/cadet_web/admin_controllers/admin_teams_controller.ex (1)

93-94: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline lib/cadet_web/admin_controllers/admin_teams_controller.ex --items all
ast-grep outline lib/cadet/accounts/teams.ex --items all

rg -n -C 5 'AdminTeamsController|/admin/teams|teamid|teamId' lib test
rg -n -C 8 'def\s+delete_team\b|def\s+delete\b' \
  lib/cadet/accounts/teams.ex \
  lib/cadet_web/admin_controllers/admin_teams_controller.ex

Repository: source-academy/backend

Length of output: 37369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== router admin stack =="
sed -n '240,292p' lib/cadet_web/router.ex | cat -n

echo "== admin_teams_controller relevant section =="
sed -n '1,100p' lib/cadet_web/admin_controllers/admin_teams_controller.ex | cat -n

echo "== admin_teams_controller_test delete section =="
sed -n '250,340p' test/cadet_web/admin_controllers/admin_teams_controller_test.exs | cat -n

echo "== pipeline / authentication checks =="
fd -a '.*\.ex$' lib/cadet_web lib/cadet | xargs rg -n "pipe_through|authenticate|authorize|roles?|student_id|admin|Course|course_id|authorization" lib/cadet_web test/cadet_web -S | head -n 200

Repository: source-academy/backend

Length of output: 33952


Ensure the deleted team belongs to the requested course.

This code discards course_id and deletes any Team with the supplied teamId. A staff/admin user authorized for one course can delete a team from another course by supplying its ID. Load the team via its assessment and return 404/403 unless team.assessment.course_id == _course_id.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet_web/admin_controllers/admin_teams_controller.ex` around lines 93 -
94, Update the course-scoped delete clause in AdminTeamsController.delete to
load the team through its assessment and validate that assessment.course_id
matches the requested course_id before deletion. Return the existing 404/403
response when the team is missing or belongs to another course, and only
delegate to the team-id deletion path after this authorization check.
lib/cadet_web/controllers/generate_ai_comments.ex (1)

53-53: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restore validation for an empty LLM API key.

An API key that decrypts to "" now passes check_llm_grading_parameters/3. The controller then calls OpenAI.chat_completion/2 with an empty api_key instead of returning a configuration error. Validate the decrypted key before the LLM request.

Proposed fix
-defp check_llm_grading_parameters(llm_model, llm_api_url, llm_course_level_prompt) do
+defp check_llm_grading_parameters(llm_api_key, llm_model, llm_api_url, llm_course_level_prompt) do
  cond do
+   is_nil(llm_api_key) or llm_api_key == "" ->
+     {:parameter_error, "LLM API key is not configured for this course"}
+
    is_nil(llm_model) or llm_model == "" ->
      {:parameter_error, "LLM model is not configured for this course"}
check_llm_grading_parameters(
+  key,
   course.llm_model,
   course.llm_api_url,
   course.llm_course_level_prompt
)

Also applies to: 90-94

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet_web/controllers/generate_ai_comments.ex` at line 53, Update
check_llm_grading_parameters/3 to validate that the decrypted LLM API key is
non-empty before allowing the grading request to proceed. Return the existing
configuration-error result for an empty key, while preserving the current
behavior for valid keys and other parameter checks.
lib/cadet_web/plug/cors.ex (1)

42-44: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd --extension exs . config -x rg -n -C 2 'CadetWeb\.Endpoint|cors_endpoints' {}

Repository: source-academy/backend

Length of output: 1287


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching cors/ex config:"
fd -i 'cors|corsica|endpoint' . | sed -n '1,120p'

echo
echo "Search cors_endpoints and corsica usage:"
rg -n -C 4 'cors_endpoints|Corsica|corsica|allow_credentials' .

Repository: source-academy/backend

Length of output: 7343


🌐 Web query:

hexdocs corsica 2.1 origins allow_credentials wildcard behavior

💡 Result:

In the Corsica Elixir library (version 2.1), when :origins is set to the wildcard "*" and :allow_credentials is set to true, the library automatically handles the conflict between the CORS specification and security requirements by dynamically echoing the origin [1][2]. Specifically, per the W3C CORS specification, the Access-Control-Allow-Origin header cannot be "*" when Access-Control-Allow-Credentials is true [1][2]. To ensure compliance while still enabling the requested behavior, Corsica will set the Access-Control-Allow-Origin header to the value of the Origin request header instead of the literal "*" [1][2]. This allows the request to succeed while strictly adhering to browser security policies [1][2].

Citations:


Require explicit CORS origins and reject wildcards when credentials are allowed.

The fallback at lib/cadet_web/plug/cors.ex:44 lets runtimes without :cors_endpoints use Corsica’s wildcard origin handling with allow_credentials: true, so any request origin can receive credentialed responses. Fail closed or require an explicit origin list in production and reject "*".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet_web/plug/cors.ex` around lines 42 - 44, Update the CORS
configuration lookup in the endpoint settings flow to require an explicit
:cors_endpoints value instead of defaulting to "*". Reject "*" when credentialed
CORS is enabled, while preserving valid explicit origin-list handling and
failing closed for missing or invalid configuration.
lib/cadet/jobs/autograder/lambda_worker.ex (1)

75-79: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle the Oban.insert/1 result.

enqueue_result_store/1 discards the return value, and run_with_models/1 then returns :ok. If the insert returns {:error, changeset}, the autograding result is silently dropped, the Oban job is recorded as completed, and the answer stays at autograding_status: :processing forever. The same applies to the call in handle_failure/4 at Line 91.

🛠️ Proposed fix
 defp enqueue_result_store(args) do
-    args
-    |> ResultStoreWorker.new()
-    |> Oban.insert()
+    case args |> ResultStoreWorker.new() |> Oban.insert() do
+      {:ok, job} ->
+        {:ok, job}
+
+      {:error, reason} ->
+        message =
+          "Failed to enqueue autograder result. answer_id: #{get_arg(args, :answer_id)}, " <>
+            "reason: #{inspect(reason)}"
+
+        Logger.error(message)
+        Sentry.capture_message(message)
+        {:error, message}
+    end
   end

Then propagate the error from run_with_models/1 instead of returning :ok unconditionally.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet/jobs/autograder/lambda_worker.ex` around lines 75 - 79, Update
enqueue_result_store/1 to propagate the result of Oban.insert/1 instead of
discarding it, and make run_with_models/1 return that result rather than
unconditionally returning :ok. Apply the same result handling to the
enqueue_result_store/1 call in handle_failure/4 so insertion errors propagate in
both success and failure paths.
lib/cadet/jobs/autograder/result_store_worker.ex (1)

67-78: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a fallback clause for unknown statuses.

normalize_status/1 matches only "success", "failed", and atoms. Any other string raises FunctionClauseError inside run/1. This worker has no rescue, and max_attempts: 1 discards the job, so the answer stays in :processing with no stored result. normalize_result/1 also crashes if result is nil because there is no non-map clause.

🛡️ Proposed fix
   defp normalize_result(result) when is_map(result) do
     %{
       score: get_arg(result, :score),
       max_score: get_arg(result, :max_score),
       status: normalize_status(get_arg(result, :status)),
       result: get_arg(result, :result)
     }
   end
 
   defp normalize_status("success"), do: :success
   defp normalize_status("failed"), do: :failed
   defp normalize_status(status) when is_atom(status), do: status
+  defp normalize_status(status), do: raise(ArgumentError, "unknown autograding status: #{inspect(status)}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cadet/jobs/autograder/result_store_worker.ex` around lines 67 - 78, Add
fallback handling in normalize_status/1 for unknown string or other status
values, mapping them to a safe default status instead of raising and leaving the
answer in :processing; also add a non-map clause in normalize_result/1 so nil
results are normalized without crashing. Preserve the existing mappings for
"success", "failed", and atom statuses.

@RichDom2185
RichDom2185 force-pushed the conductor-assessments branch from 3999d4b to aa6105b Compare August 1, 2026 05:04
Conductor programming questions now build a grader request carrying
{format, language, evaluator} instead of raising, so they are autograded
by the external conductor runtime like legacy questions.
@RichDom2185
RichDom2185 force-pushed the conductor-assessments branch from aa6105b to 0fcb3dd Compare August 1, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants