fix(quiz/graph): route mastery writes through one path + refresh course context (#128) - #242

Closed
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity
Closed

fix(quiz/graph): route mastery writes through one path + refresh course context (#128)#242
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

What & why

Knowledge-graph write-integrity fixes from the backend audit — issue #128 (findings #6/#9/#24).

#6 (HIGH) — quiz bypassed apply_graph_update + left course context stale

submit_quiz wrote graph_nodes directly and re-implemented mastery/event logic, and never called update_course_context, so the shared per-course aggregate went stale after every quiz.

  • Extracted a single sanctioned primitive, graph_service.apply_mastery_event(node, delta, *, reason, event_type, user_id) — clamp → capped mastery_events append (effective delta) → bump times_studied/last_studied_at.
  • Bothsubmit_quizandapply_graph_update's updated_nodes loop now route through it → mastery logic lives in one place (honors the "graph writes go through one path" convention).
  • submit_quiz now backgrounds update_course_context(course_id) so the aggregate refreshes without blocking the response.

#9 (MEDIUM) — non-atomic mastery read-modify-write — deferred

Still a non-atomic RMW, but now on a single code path with an inline NOTE. The DB-side atomic append (Postgres RPC) needs migration-runner plumbing and is a follow-up tied to #195/#197. (Deferred deliberately — see discussion on #128.)

#24 (LOW) — directional edge dedup

Dedup now ignores orientation for symmetric relationship types (related/similar) while keeping directional types (prerequisite/builds_on) distinct.

Security note

This branch is rebased on top of the quiz IDOR fix (b3952f0). apply_mastery_event takes an optional user_id so the write stays owner-scoped — the IDOR defense-in-depth is preserved, not weakened, and apply_graph_update now passes user_id too.

Testing

  • TDD: failing tests first, then implementation.
  • +10 new testsapply_mastery_event (incl. clamping, capped events, effective delta, user_id scoping), edge-orientation dedup, quiz course-context refresh.
  • Full backend suite green (718 passed); ruff check . clean (CI ratchet).

Follow-up

Addresses #128.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Quiz submissions now update course context more efficiently in the background after mastery changes.
    • Mastery updates are now applied via a shared graph primitive, ensuring consistent clamping, tier recalculation, and tracked mastery events.
    • Graph edge creation now deduplicates both orientations for symmetric relationship types.
  • Tests

    • Added unit coverage for mastery update behavior (including event history limits and effective deltas).
    • Expanded coverage for edge deduplication/orientation rules and quiz context refresh behavior.

…se context (#128)
Knowledge-graph write-integrity fixes from the backend audit (#128):
- #6 (HIGH): quiz submit no longer writes graph_nodes directly while
re-implementing mastery/event logic. Quiz scoring and apply_graph_update's
updated_nodes loop now both route through a single sanctioned primitive,
graph_service.apply_mastery_event, and quiz submit backgrounds
update_course_context() so the per-course aggregate no longer goes stale
after a quiz.
- #9 (MEDIUM): deferred per decision. The read-modify-write is still
non-atomic, but now lives on one code path with an inline TODO; the
DB-side atomic append is a follow-up tied to the migration-runner (#197).
- #24 (LOW): edge dedup now ignores orientation for symmetric relationship
types (related/similar) while keeping directional types
(prerequisite/builds_on) distinct.
apply_mastery_event takes an optional user_id so the write stays
owner-scoped, preserving the IDOR defense-in-depth from b3952f0.
Tests: +10 (apply_mastery_event incl. user_id scoping, edge orientation,
quiz course-context refresh). Full backend suite green (718 passed); ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 91369f29-97fd-45f5-94f2-aea152351ab1

📥 Commits

Reviewing files that changed from the base of the PR and between 2088493 and ac25d0e.

📒 Files selected for processing (1)
  • backend/routes/quiz.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/routes/quiz.py

📝 Walkthrough

Walkthrough

Introduces apply_mastery_event in graph_service.py as a centralized primitive for clamped mastery writes, mastery tier recomputation, and capped event recording. apply_graph_update and submit_quiz are refactored to delegate to this helper. submit_quiz additionally switches to an async background course-context refresh. Symmetric edge type deduplication is added to prevent bidirectional duplicate edges. Tests cover all three changes.

Changes

Mastery centralization, edge dedup, and quiz route wiring

Layer / File(s)Summary
apply_mastery_event helper and apply_graph_update refactor
backend/services/graph_service.py
Adds apply_mastery_event that clamps mastery score to [0,1], recomputes mastery tier, increments times_studied/last_studied_at, appends capped mastery events with effective delta, and optionally scopes the DB write by user_id. Refactors apply_graph_update's updated_nodes path to call this helper and source before/after/course_id from its return value.
Symmetric edge deduplication
backend/services/graph_service.py
Adds _SYMMETRIC_RELATIONSHIP_TYPES and extends new_edges insertion to look up the reverse-orientation edge for symmetric types, treating it as an existing duplicate and skipping the insert.
Quiz route: apply_mastery_event and async course context refresh
backend/routes/quiz.py
Updates imports, expands the owner-scoped graph_nodes query to include course_id and concept_name, replaces inline mastery/tier/event logic with a call to apply_mastery_event, and conditionally schedules update_course_context as a background task using the returned course_id.
Tests
backend/tests/test_graph_service.py, backend/tests/test_quiz_routes.py
Adds TestApplyMasteryEvent (clamping, tier, user_id scoping, event cap, effective delta, no context-refresh side effect), TestEdgeDedupOrientation (symmetric skip vs. directional insert), and TestSubmitCourseContextRefresh (context refresh called with course_id when present, skipped when absent).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐇 A rabbit once wrote scores by hand,
Scattering mastery across the land.
Now apply_mastery_event takes the wheel,
Clamped and capped — a tidy deal!
Symmetric edges no longer double,
And context refreshes without trouble.
One helper hops, and all is well! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 28.57% 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 nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main change: consolidating mastery writes through one path and adding course context refresh, which are the primary objectives of the PR (finding #6).
Description check✅ PassedThe PR description covers key requirements: what/why section explaining the fixes, changes made across multiple findings, related issues reference, testing details with test counts, and notes for reviewers about security and follow-up work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/128-graph-write-integrity

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 and usage tips.

Comment threadbackend/routes/quiz.py Fixed
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 20, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontendac25d0eCommit Preview URL

Branch Preview URL
Jun 22 2026, 02:54 AM

@coderabbitaicoderabbitaiBot 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.

🧹 Nitpick comments (2)
backend/routes/quiz.py (1)

441-446: 💤 Low value

Silent exception swallowing may hinder debugging.

The except Exception: pass pattern loses valuable diagnostic information if update_course_context fails. Consider logging at debug or warning level to aid troubleshooting without blocking the response.

This mirrors the pattern in apply_graph_update (lines 598-599), so it's consistent with existing code, but both locations could benefit from minimal logging.

🤖 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 `@backend/routes/quiz.py` around lines 441 - 446, The bare `except Exception:
pass` in the `_refresh_course_ctx` function silently swallows any exceptions
from `update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
backend/services/graph_service.py (1)

418-418: datetime.utcnow() is deprecated in Python 3.12+ (project target).

Refactor to datetime.now(timezone.utc).isoformat() for forward compatibility. Note that this changes the ISO format from 2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00, which may impact downstream consumers. This issue affects 14+ locations across graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py, learn.py, and flashcards.py—coordinate the refactor across all services to ensure consistent timestamp format.

🤖 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 `@backend/services/graph_service.py` at line 418, Replace the deprecated
datetime.utcnow().isoformat() call in the "ts" field (line 418) with
datetime.now(timezone.utc).isoformat() to ensure Python 3.12+ compatibility.
First import timezone from the datetime module at the top of the file. Then
locate all 14+ occurrences of datetime.utcnow() across graph_service.py,
social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py,
learn.py, and flashcards.py and apply the same replacement pattern consistently.
Note that this will change the timestamp format from 2024-01-01T12:00:00 to
2024-01-01T12:00:00+00:00 (with timezone offset), so ensure all downstream
consumers that parse or validate these timestamps are aware of this format
change before deploying.
🤖 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.
Nitpick comments:
In `@backend/routes/quiz.py`:
- Around line 441-446: The bare `except Exception: pass` in the
`_refresh_course_ctx` function silently swallows any exceptions from
`update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
In `@backend/services/graph_service.py`:
- Line 418: Replace the deprecated datetime.utcnow().isoformat() call in the
"ts" field (line 418) with datetime.now(timezone.utc).isoformat() to ensure
Python 3.12+ compatibility. First import timezone from the datetime module at
the top of the file. Then locate all 14+ occurrences of datetime.utcnow() across
graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py,
quiz.py, learn.py, and flashcards.py and apply the same replacement pattern
consistently. Note that this will change the timestamp format from
2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00 (with timezone offset), so
ensure all downstream consumers that parse or validate these timestamps are
aware of this format change before deploying.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e508a01f-98ee-482a-8438-12623e5dba95

📥 Commits

Reviewing files that changed from the base of the PR and between 8c71463 and 2088493.

📒 Files selected for processing (4)
  • backend/routes/quiz.py
  • backend/services/graph_service.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py

…swallowing
The background _refresh_course_ctx task swallowed all exceptions with a bare
pass, hiding aggregation/summary failures. Log via logger.exception so failures
are observable while keeping the submit flow non-blocking.
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging8b19719Commit Preview URL

Branch Preview URL
Jun 24 2026, 02:44 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by the DB modular redesign (#279), which closes #128: quiz mastery now routes through apply_graph_update, and mastery is an append-only node_mastery_events table (0023 + the graph/study slices) — no more non-atomic RMW or direct graph_nodes writes. Closing as obsolete. Reopen if not fully covered.

@AndresL230
AndresL230 deleted the fix/128-graph-write-integrity branch June 27, 2026 04:20
Sign up for freeto 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

@AndresL230@Jose-Gael-Cruz-Lopez
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(quiz/graph): route mastery writes through one path + refresh course context (#128) - #242

Closed
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity
Closed

fix(quiz/graph): route mastery writes through one path + refresh course context (#128)#242
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

What & why

Knowledge-graph write-integrity fixes from the backend audit — issue #128 (findings #6/#9/#24).

#6 (HIGH) — quiz bypassed apply_graph_update + left course context stale

submit_quiz wrote graph_nodes directly and re-implemented mastery/event logic, and never called update_course_context, so the shared per-course aggregate went stale after every quiz.

  • Extracted a single sanctioned primitive, graph_service.apply_mastery_event(node, delta, *, reason, event_type, user_id) — clamp → capped mastery_events append (effective delta) → bump times_studied/last_studied_at.
  • Bothsubmit_quizandapply_graph_update's updated_nodes loop now route through it → mastery logic lives in one place (honors the "graph writes go through one path" convention).
  • submit_quiz now backgrounds update_course_context(course_id) so the aggregate refreshes without blocking the response.

#9 (MEDIUM) — non-atomic mastery read-modify-write — deferred

Still a non-atomic RMW, but now on a single code path with an inline NOTE. The DB-side atomic append (Postgres RPC) needs migration-runner plumbing and is a follow-up tied to #195/#197. (Deferred deliberately — see discussion on #128.)

#24 (LOW) — directional edge dedup

Dedup now ignores orientation for symmetric relationship types (related/similar) while keeping directional types (prerequisite/builds_on) distinct.

Security note

This branch is rebased on top of the quiz IDOR fix (b3952f0). apply_mastery_event takes an optional user_id so the write stays owner-scoped — the IDOR defense-in-depth is preserved, not weakened, and apply_graph_update now passes user_id too.

Testing

  • TDD: failing tests first, then implementation.
  • +10 new testsapply_mastery_event (incl. clamping, capped events, effective delta, user_id scoping), edge-orientation dedup, quiz course-context refresh.
  • Full backend suite green (718 passed); ruff check . clean (CI ratchet).

Follow-up

Addresses #128.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Quiz submissions now update course context more efficiently in the background after mastery changes.
    • Mastery updates are now applied via a shared graph primitive, ensuring consistent clamping, tier recalculation, and tracked mastery events.
    • Graph edge creation now deduplicates both orientations for symmetric relationship types.
  • Tests

    • Added unit coverage for mastery update behavior (including event history limits and effective deltas).
    • Expanded coverage for edge deduplication/orientation rules and quiz context refresh behavior.

…se context (#128)
Knowledge-graph write-integrity fixes from the backend audit (#128):
- #6 (HIGH): quiz submit no longer writes graph_nodes directly while
re-implementing mastery/event logic. Quiz scoring and apply_graph_update's
updated_nodes loop now both route through a single sanctioned primitive,
graph_service.apply_mastery_event, and quiz submit backgrounds
update_course_context() so the per-course aggregate no longer goes stale
after a quiz.
- #9 (MEDIUM): deferred per decision. The read-modify-write is still
non-atomic, but now lives on one code path with an inline TODO; the
DB-side atomic append is a follow-up tied to the migration-runner (#197).
- #24 (LOW): edge dedup now ignores orientation for symmetric relationship
types (related/similar) while keeping directional types
(prerequisite/builds_on) distinct.
apply_mastery_event takes an optional user_id so the write stays
owner-scoped, preserving the IDOR defense-in-depth from b3952f0.
Tests: +10 (apply_mastery_event incl. user_id scoping, edge orientation,
quiz course-context refresh). Full backend suite green (718 passed); ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 91369f29-97fd-45f5-94f2-aea152351ab1

📥 Commits

Reviewing files that changed from the base of the PR and between 2088493 and ac25d0e.

📒 Files selected for processing (1)
  • backend/routes/quiz.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/routes/quiz.py

📝 Walkthrough

Walkthrough

Introduces apply_mastery_event in graph_service.py as a centralized primitive for clamped mastery writes, mastery tier recomputation, and capped event recording. apply_graph_update and submit_quiz are refactored to delegate to this helper. submit_quiz additionally switches to an async background course-context refresh. Symmetric edge type deduplication is added to prevent bidirectional duplicate edges. Tests cover all three changes.

Changes

Mastery centralization, edge dedup, and quiz route wiring

Layer / File(s)Summary
apply_mastery_event helper and apply_graph_update refactor
backend/services/graph_service.py
Adds apply_mastery_event that clamps mastery score to [0,1], recomputes mastery tier, increments times_studied/last_studied_at, appends capped mastery events with effective delta, and optionally scopes the DB write by user_id. Refactors apply_graph_update's updated_nodes path to call this helper and source before/after/course_id from its return value.
Symmetric edge deduplication
backend/services/graph_service.py
Adds _SYMMETRIC_RELATIONSHIP_TYPES and extends new_edges insertion to look up the reverse-orientation edge for symmetric types, treating it as an existing duplicate and skipping the insert.
Quiz route: apply_mastery_event and async course context refresh
backend/routes/quiz.py
Updates imports, expands the owner-scoped graph_nodes query to include course_id and concept_name, replaces inline mastery/tier/event logic with a call to apply_mastery_event, and conditionally schedules update_course_context as a background task using the returned course_id.
Tests
backend/tests/test_graph_service.py, backend/tests/test_quiz_routes.py
Adds TestApplyMasteryEvent (clamping, tier, user_id scoping, event cap, effective delta, no context-refresh side effect), TestEdgeDedupOrientation (symmetric skip vs. directional insert), and TestSubmitCourseContextRefresh (context refresh called with course_id when present, skipped when absent).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐇 A rabbit once wrote scores by hand,
Scattering mastery across the land.
Now apply_mastery_event takes the wheel,
Clamped and capped — a tidy deal!
Symmetric edges no longer double,
And context refreshes without trouble.
One helper hops, and all is well! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 28.57% 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 nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main change: consolidating mastery writes through one path and adding course context refresh, which are the primary objectives of the PR (finding #6).
Description check✅ PassedThe PR description covers key requirements: what/why section explaining the fixes, changes made across multiple findings, related issues reference, testing details with test counts, and notes for reviewers about security and follow-up work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/128-graph-write-integrity

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 and usage tips.

Comment threadbackend/routes/quiz.py Fixed
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 20, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontendac25d0eCommit Preview URL

Branch Preview URL
Jun 22 2026, 02:54 AM

@coderabbitaicoderabbitaiBot 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.

🧹 Nitpick comments (2)
backend/routes/quiz.py (1)

441-446: 💤 Low value

Silent exception swallowing may hinder debugging.

The except Exception: pass pattern loses valuable diagnostic information if update_course_context fails. Consider logging at debug or warning level to aid troubleshooting without blocking the response.

This mirrors the pattern in apply_graph_update (lines 598-599), so it's consistent with existing code, but both locations could benefit from minimal logging.

🤖 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 `@backend/routes/quiz.py` around lines 441 - 446, The bare `except Exception:
pass` in the `_refresh_course_ctx` function silently swallows any exceptions
from `update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
backend/services/graph_service.py (1)

418-418: datetime.utcnow() is deprecated in Python 3.12+ (project target).

Refactor to datetime.now(timezone.utc).isoformat() for forward compatibility. Note that this changes the ISO format from 2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00, which may impact downstream consumers. This issue affects 14+ locations across graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py, learn.py, and flashcards.py—coordinate the refactor across all services to ensure consistent timestamp format.

🤖 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 `@backend/services/graph_service.py` at line 418, Replace the deprecated
datetime.utcnow().isoformat() call in the "ts" field (line 418) with
datetime.now(timezone.utc).isoformat() to ensure Python 3.12+ compatibility.
First import timezone from the datetime module at the top of the file. Then
locate all 14+ occurrences of datetime.utcnow() across graph_service.py,
social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py,
learn.py, and flashcards.py and apply the same replacement pattern consistently.
Note that this will change the timestamp format from 2024-01-01T12:00:00 to
2024-01-01T12:00:00+00:00 (with timezone offset), so ensure all downstream
consumers that parse or validate these timestamps are aware of this format
change before deploying.
🤖 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.
Nitpick comments:
In `@backend/routes/quiz.py`:
- Around line 441-446: The bare `except Exception: pass` in the
`_refresh_course_ctx` function silently swallows any exceptions from
`update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
In `@backend/services/graph_service.py`:
- Line 418: Replace the deprecated datetime.utcnow().isoformat() call in the
"ts" field (line 418) with datetime.now(timezone.utc).isoformat() to ensure
Python 3.12+ compatibility. First import timezone from the datetime module at
the top of the file. Then locate all 14+ occurrences of datetime.utcnow() across
graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py,
quiz.py, learn.py, and flashcards.py and apply the same replacement pattern
consistently. Note that this will change the timestamp format from
2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00 (with timezone offset), so
ensure all downstream consumers that parse or validate these timestamps are
aware of this format change before deploying.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e508a01f-98ee-482a-8438-12623e5dba95

📥 Commits

Reviewing files that changed from the base of the PR and between 8c71463 and 2088493.

📒 Files selected for processing (4)
  • backend/routes/quiz.py
  • backend/services/graph_service.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py

…swallowing
The background _refresh_course_ctx task swallowed all exceptions with a bare
pass, hiding aggregation/summary failures. Log via logger.exception so failures
are observable while keeping the submit flow non-blocking.
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging8b19719Commit Preview URL

Branch Preview URL
Jun 24 2026, 02:44 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by the DB modular redesign (#279), which closes #128: quiz mastery now routes through apply_graph_update, and mastery is an append-only node_mastery_events table (0023 + the graph/study slices) — no more non-atomic RMW or direct graph_nodes writes. Closing as obsolete. Reopen if not fully covered.

@AndresL230
AndresL230 deleted the fix/128-graph-write-integrity branch June 27, 2026 04:20
Sign up for freeto 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

@AndresL230@Jose-Gael-Cruz-Lopez
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(quiz/graph): route mastery writes through one path + refresh course context (#128) - #242

Closed
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity
Closed

fix(quiz/graph): route mastery writes through one path + refresh course context (#128)#242
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

What & why

Knowledge-graph write-integrity fixes from the backend audit — issue #128 (findings #6/#9/#24).

#6 (HIGH) — quiz bypassed apply_graph_update + left course context stale

submit_quiz wrote graph_nodes directly and re-implemented mastery/event logic, and never called update_course_context, so the shared per-course aggregate went stale after every quiz.

  • Extracted a single sanctioned primitive, graph_service.apply_mastery_event(node, delta, *, reason, event_type, user_id) — clamp → capped mastery_events append (effective delta) → bump times_studied/last_studied_at.
  • Bothsubmit_quizandapply_graph_update's updated_nodes loop now route through it → mastery logic lives in one place (honors the "graph writes go through one path" convention).
  • submit_quiz now backgrounds update_course_context(course_id) so the aggregate refreshes without blocking the response.

#9 (MEDIUM) — non-atomic mastery read-modify-write — deferred

Still a non-atomic RMW, but now on a single code path with an inline NOTE. The DB-side atomic append (Postgres RPC) needs migration-runner plumbing and is a follow-up tied to #195/#197. (Deferred deliberately — see discussion on #128.)

#24 (LOW) — directional edge dedup

Dedup now ignores orientation for symmetric relationship types (related/similar) while keeping directional types (prerequisite/builds_on) distinct.

Security note

This branch is rebased on top of the quiz IDOR fix (b3952f0). apply_mastery_event takes an optional user_id so the write stays owner-scoped — the IDOR defense-in-depth is preserved, not weakened, and apply_graph_update now passes user_id too.

Testing

  • TDD: failing tests first, then implementation.
  • +10 new testsapply_mastery_event (incl. clamping, capped events, effective delta, user_id scoping), edge-orientation dedup, quiz course-context refresh.
  • Full backend suite green (718 passed); ruff check . clean (CI ratchet).

Follow-up

Addresses #128.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Quiz submissions now update course context more efficiently in the background after mastery changes.
    • Mastery updates are now applied via a shared graph primitive, ensuring consistent clamping, tier recalculation, and tracked mastery events.
    • Graph edge creation now deduplicates both orientations for symmetric relationship types.
  • Tests

    • Added unit coverage for mastery update behavior (including event history limits and effective deltas).
    • Expanded coverage for edge deduplication/orientation rules and quiz context refresh behavior.

…se context (#128)
Knowledge-graph write-integrity fixes from the backend audit (#128):
- #6 (HIGH): quiz submit no longer writes graph_nodes directly while
re-implementing mastery/event logic. Quiz scoring and apply_graph_update's
updated_nodes loop now both route through a single sanctioned primitive,
graph_service.apply_mastery_event, and quiz submit backgrounds
update_course_context() so the per-course aggregate no longer goes stale
after a quiz.
- #9 (MEDIUM): deferred per decision. The read-modify-write is still
non-atomic, but now lives on one code path with an inline TODO; the
DB-side atomic append is a follow-up tied to the migration-runner (#197).
- #24 (LOW): edge dedup now ignores orientation for symmetric relationship
types (related/similar) while keeping directional types
(prerequisite/builds_on) distinct.
apply_mastery_event takes an optional user_id so the write stays
owner-scoped, preserving the IDOR defense-in-depth from b3952f0.
Tests: +10 (apply_mastery_event incl. user_id scoping, edge orientation,
quiz course-context refresh). Full backend suite green (718 passed); ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 91369f29-97fd-45f5-94f2-aea152351ab1

📥 Commits

Reviewing files that changed from the base of the PR and between 2088493 and ac25d0e.

📒 Files selected for processing (1)
  • backend/routes/quiz.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/routes/quiz.py

📝 Walkthrough

Walkthrough

Introduces apply_mastery_event in graph_service.py as a centralized primitive for clamped mastery writes, mastery tier recomputation, and capped event recording. apply_graph_update and submit_quiz are refactored to delegate to this helper. submit_quiz additionally switches to an async background course-context refresh. Symmetric edge type deduplication is added to prevent bidirectional duplicate edges. Tests cover all three changes.

Changes

Mastery centralization, edge dedup, and quiz route wiring

Layer / File(s)Summary
apply_mastery_event helper and apply_graph_update refactor
backend/services/graph_service.py
Adds apply_mastery_event that clamps mastery score to [0,1], recomputes mastery tier, increments times_studied/last_studied_at, appends capped mastery events with effective delta, and optionally scopes the DB write by user_id. Refactors apply_graph_update's updated_nodes path to call this helper and source before/after/course_id from its return value.
Symmetric edge deduplication
backend/services/graph_service.py
Adds _SYMMETRIC_RELATIONSHIP_TYPES and extends new_edges insertion to look up the reverse-orientation edge for symmetric types, treating it as an existing duplicate and skipping the insert.
Quiz route: apply_mastery_event and async course context refresh
backend/routes/quiz.py
Updates imports, expands the owner-scoped graph_nodes query to include course_id and concept_name, replaces inline mastery/tier/event logic with a call to apply_mastery_event, and conditionally schedules update_course_context as a background task using the returned course_id.
Tests
backend/tests/test_graph_service.py, backend/tests/test_quiz_routes.py
Adds TestApplyMasteryEvent (clamping, tier, user_id scoping, event cap, effective delta, no context-refresh side effect), TestEdgeDedupOrientation (symmetric skip vs. directional insert), and TestSubmitCourseContextRefresh (context refresh called with course_id when present, skipped when absent).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐇 A rabbit once wrote scores by hand,
Scattering mastery across the land.
Now apply_mastery_event takes the wheel,
Clamped and capped — a tidy deal!
Symmetric edges no longer double,
And context refreshes without trouble.
One helper hops, and all is well! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 28.57% 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 nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main change: consolidating mastery writes through one path and adding course context refresh, which are the primary objectives of the PR (finding #6).
Description check✅ PassedThe PR description covers key requirements: what/why section explaining the fixes, changes made across multiple findings, related issues reference, testing details with test counts, and notes for reviewers about security and follow-up work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/128-graph-write-integrity

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 and usage tips.

Comment threadbackend/routes/quiz.py Fixed
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 20, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontendac25d0eCommit Preview URL

Branch Preview URL
Jun 22 2026, 02:54 AM

@coderabbitaicoderabbitaiBot 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.

🧹 Nitpick comments (2)
backend/routes/quiz.py (1)

441-446: 💤 Low value

Silent exception swallowing may hinder debugging.

The except Exception: pass pattern loses valuable diagnostic information if update_course_context fails. Consider logging at debug or warning level to aid troubleshooting without blocking the response.

This mirrors the pattern in apply_graph_update (lines 598-599), so it's consistent with existing code, but both locations could benefit from minimal logging.

🤖 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 `@backend/routes/quiz.py` around lines 441 - 446, The bare `except Exception:
pass` in the `_refresh_course_ctx` function silently swallows any exceptions
from `update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
backend/services/graph_service.py (1)

418-418: datetime.utcnow() is deprecated in Python 3.12+ (project target).

Refactor to datetime.now(timezone.utc).isoformat() for forward compatibility. Note that this changes the ISO format from 2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00, which may impact downstream consumers. This issue affects 14+ locations across graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py, learn.py, and flashcards.py—coordinate the refactor across all services to ensure consistent timestamp format.

🤖 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 `@backend/services/graph_service.py` at line 418, Replace the deprecated
datetime.utcnow().isoformat() call in the "ts" field (line 418) with
datetime.now(timezone.utc).isoformat() to ensure Python 3.12+ compatibility.
First import timezone from the datetime module at the top of the file. Then
locate all 14+ occurrences of datetime.utcnow() across graph_service.py,
social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py,
learn.py, and flashcards.py and apply the same replacement pattern consistently.
Note that this will change the timestamp format from 2024-01-01T12:00:00 to
2024-01-01T12:00:00+00:00 (with timezone offset), so ensure all downstream
consumers that parse or validate these timestamps are aware of this format
change before deploying.
🤖 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.
Nitpick comments:
In `@backend/routes/quiz.py`:
- Around line 441-446: The bare `except Exception: pass` in the
`_refresh_course_ctx` function silently swallows any exceptions from
`update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
In `@backend/services/graph_service.py`:
- Line 418: Replace the deprecated datetime.utcnow().isoformat() call in the
"ts" field (line 418) with datetime.now(timezone.utc).isoformat() to ensure
Python 3.12+ compatibility. First import timezone from the datetime module at
the top of the file. Then locate all 14+ occurrences of datetime.utcnow() across
graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py,
quiz.py, learn.py, and flashcards.py and apply the same replacement pattern
consistently. Note that this will change the timestamp format from
2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00 (with timezone offset), so
ensure all downstream consumers that parse or validate these timestamps are
aware of this format change before deploying.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e508a01f-98ee-482a-8438-12623e5dba95

📥 Commits

Reviewing files that changed from the base of the PR and between 8c71463 and 2088493.

📒 Files selected for processing (4)
  • backend/routes/quiz.py
  • backend/services/graph_service.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py

…swallowing
The background _refresh_course_ctx task swallowed all exceptions with a bare
pass, hiding aggregation/summary failures. Log via logger.exception so failures
are observable while keeping the submit flow non-blocking.
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging8b19719Commit Preview URL

Branch Preview URL
Jun 24 2026, 02:44 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by the DB modular redesign (#279), which closes #128: quiz mastery now routes through apply_graph_update, and mastery is an append-only node_mastery_events table (0023 + the graph/study slices) — no more non-atomic RMW or direct graph_nodes writes. Closing as obsolete. Reopen if not fully covered.

@AndresL230
AndresL230 deleted the fix/128-graph-write-integrity branch June 27, 2026 04:20
Sign up for freeto 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

@AndresL230@Jose-Gael-Cruz-Lopez
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(quiz/graph): route mastery writes through one path + refresh course context (#128) - #242

Closed
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity
Closed

fix(quiz/graph): route mastery writes through one path + refresh course context (#128)#242
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

What & why

Knowledge-graph write-integrity fixes from the backend audit — issue #128 (findings #6/#9/#24).

#6 (HIGH) — quiz bypassed apply_graph_update + left course context stale

submit_quiz wrote graph_nodes directly and re-implemented mastery/event logic, and never called update_course_context, so the shared per-course aggregate went stale after every quiz.

  • Extracted a single sanctioned primitive, graph_service.apply_mastery_event(node, delta, *, reason, event_type, user_id) — clamp → capped mastery_events append (effective delta) → bump times_studied/last_studied_at.
  • Bothsubmit_quizandapply_graph_update's updated_nodes loop now route through it → mastery logic lives in one place (honors the "graph writes go through one path" convention).
  • submit_quiz now backgrounds update_course_context(course_id) so the aggregate refreshes without blocking the response.

#9 (MEDIUM) — non-atomic mastery read-modify-write — deferred

Still a non-atomic RMW, but now on a single code path with an inline NOTE. The DB-side atomic append (Postgres RPC) needs migration-runner plumbing and is a follow-up tied to #195/#197. (Deferred deliberately — see discussion on #128.)

#24 (LOW) — directional edge dedup

Dedup now ignores orientation for symmetric relationship types (related/similar) while keeping directional types (prerequisite/builds_on) distinct.

Security note

This branch is rebased on top of the quiz IDOR fix (b3952f0). apply_mastery_event takes an optional user_id so the write stays owner-scoped — the IDOR defense-in-depth is preserved, not weakened, and apply_graph_update now passes user_id too.

Testing

  • TDD: failing tests first, then implementation.
  • +10 new testsapply_mastery_event (incl. clamping, capped events, effective delta, user_id scoping), edge-orientation dedup, quiz course-context refresh.
  • Full backend suite green (718 passed); ruff check . clean (CI ratchet).

Follow-up

Addresses #128.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Quiz submissions now update course context more efficiently in the background after mastery changes.
    • Mastery updates are now applied via a shared graph primitive, ensuring consistent clamping, tier recalculation, and tracked mastery events.
    • Graph edge creation now deduplicates both orientations for symmetric relationship types.
  • Tests

    • Added unit coverage for mastery update behavior (including event history limits and effective deltas).
    • Expanded coverage for edge deduplication/orientation rules and quiz context refresh behavior.

…se context (#128)
Knowledge-graph write-integrity fixes from the backend audit (#128):
- #6 (HIGH): quiz submit no longer writes graph_nodes directly while
re-implementing mastery/event logic. Quiz scoring and apply_graph_update's
updated_nodes loop now both route through a single sanctioned primitive,
graph_service.apply_mastery_event, and quiz submit backgrounds
update_course_context() so the per-course aggregate no longer goes stale
after a quiz.
- #9 (MEDIUM): deferred per decision. The read-modify-write is still
non-atomic, but now lives on one code path with an inline TODO; the
DB-side atomic append is a follow-up tied to the migration-runner (#197).
- #24 (LOW): edge dedup now ignores orientation for symmetric relationship
types (related/similar) while keeping directional types
(prerequisite/builds_on) distinct.
apply_mastery_event takes an optional user_id so the write stays
owner-scoped, preserving the IDOR defense-in-depth from b3952f0.
Tests: +10 (apply_mastery_event incl. user_id scoping, edge orientation,
quiz course-context refresh). Full backend suite green (718 passed); ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 91369f29-97fd-45f5-94f2-aea152351ab1

📥 Commits

Reviewing files that changed from the base of the PR and between 2088493 and ac25d0e.

📒 Files selected for processing (1)
  • backend/routes/quiz.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/routes/quiz.py

📝 Walkthrough

Walkthrough

Introduces apply_mastery_event in graph_service.py as a centralized primitive for clamped mastery writes, mastery tier recomputation, and capped event recording. apply_graph_update and submit_quiz are refactored to delegate to this helper. submit_quiz additionally switches to an async background course-context refresh. Symmetric edge type deduplication is added to prevent bidirectional duplicate edges. Tests cover all three changes.

Changes

Mastery centralization, edge dedup, and quiz route wiring

Layer / File(s)Summary
apply_mastery_event helper and apply_graph_update refactor
backend/services/graph_service.py
Adds apply_mastery_event that clamps mastery score to [0,1], recomputes mastery tier, increments times_studied/last_studied_at, appends capped mastery events with effective delta, and optionally scopes the DB write by user_id. Refactors apply_graph_update's updated_nodes path to call this helper and source before/after/course_id from its return value.
Symmetric edge deduplication
backend/services/graph_service.py
Adds _SYMMETRIC_RELATIONSHIP_TYPES and extends new_edges insertion to look up the reverse-orientation edge for symmetric types, treating it as an existing duplicate and skipping the insert.
Quiz route: apply_mastery_event and async course context refresh
backend/routes/quiz.py
Updates imports, expands the owner-scoped graph_nodes query to include course_id and concept_name, replaces inline mastery/tier/event logic with a call to apply_mastery_event, and conditionally schedules update_course_context as a background task using the returned course_id.
Tests
backend/tests/test_graph_service.py, backend/tests/test_quiz_routes.py
Adds TestApplyMasteryEvent (clamping, tier, user_id scoping, event cap, effective delta, no context-refresh side effect), TestEdgeDedupOrientation (symmetric skip vs. directional insert), and TestSubmitCourseContextRefresh (context refresh called with course_id when present, skipped when absent).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐇 A rabbit once wrote scores by hand,
Scattering mastery across the land.
Now apply_mastery_event takes the wheel,
Clamped and capped — a tidy deal!
Symmetric edges no longer double,
And context refreshes without trouble.
One helper hops, and all is well! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 28.57% 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 nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main change: consolidating mastery writes through one path and adding course context refresh, which are the primary objectives of the PR (finding #6).
Description check✅ PassedThe PR description covers key requirements: what/why section explaining the fixes, changes made across multiple findings, related issues reference, testing details with test counts, and notes for reviewers about security and follow-up work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/128-graph-write-integrity

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 and usage tips.

Comment threadbackend/routes/quiz.py Fixed
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 20, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontendac25d0eCommit Preview URL

Branch Preview URL
Jun 22 2026, 02:54 AM

@coderabbitaicoderabbitaiBot 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.

🧹 Nitpick comments (2)
backend/routes/quiz.py (1)

441-446: 💤 Low value

Silent exception swallowing may hinder debugging.

The except Exception: pass pattern loses valuable diagnostic information if update_course_context fails. Consider logging at debug or warning level to aid troubleshooting without blocking the response.

This mirrors the pattern in apply_graph_update (lines 598-599), so it's consistent with existing code, but both locations could benefit from minimal logging.

🤖 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 `@backend/routes/quiz.py` around lines 441 - 446, The bare `except Exception:
pass` in the `_refresh_course_ctx` function silently swallows any exceptions
from `update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
backend/services/graph_service.py (1)

418-418: datetime.utcnow() is deprecated in Python 3.12+ (project target).

Refactor to datetime.now(timezone.utc).isoformat() for forward compatibility. Note that this changes the ISO format from 2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00, which may impact downstream consumers. This issue affects 14+ locations across graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py, learn.py, and flashcards.py—coordinate the refactor across all services to ensure consistent timestamp format.

🤖 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 `@backend/services/graph_service.py` at line 418, Replace the deprecated
datetime.utcnow().isoformat() call in the "ts" field (line 418) with
datetime.now(timezone.utc).isoformat() to ensure Python 3.12+ compatibility.
First import timezone from the datetime module at the top of the file. Then
locate all 14+ occurrences of datetime.utcnow() across graph_service.py,
social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py,
learn.py, and flashcards.py and apply the same replacement pattern consistently.
Note that this will change the timestamp format from 2024-01-01T12:00:00 to
2024-01-01T12:00:00+00:00 (with timezone offset), so ensure all downstream
consumers that parse or validate these timestamps are aware of this format
change before deploying.
🤖 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.
Nitpick comments:
In `@backend/routes/quiz.py`:
- Around line 441-446: The bare `except Exception: pass` in the
`_refresh_course_ctx` function silently swallows any exceptions from
`update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
In `@backend/services/graph_service.py`:
- Line 418: Replace the deprecated datetime.utcnow().isoformat() call in the
"ts" field (line 418) with datetime.now(timezone.utc).isoformat() to ensure
Python 3.12+ compatibility. First import timezone from the datetime module at
the top of the file. Then locate all 14+ occurrences of datetime.utcnow() across
graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py,
quiz.py, learn.py, and flashcards.py and apply the same replacement pattern
consistently. Note that this will change the timestamp format from
2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00 (with timezone offset), so
ensure all downstream consumers that parse or validate these timestamps are
aware of this format change before deploying.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e508a01f-98ee-482a-8438-12623e5dba95

📥 Commits

Reviewing files that changed from the base of the PR and between 8c71463 and 2088493.

📒 Files selected for processing (4)
  • backend/routes/quiz.py
  • backend/services/graph_service.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py

…swallowing
The background _refresh_course_ctx task swallowed all exceptions with a bare
pass, hiding aggregation/summary failures. Log via logger.exception so failures
are observable while keeping the submit flow non-blocking.
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging8b19719Commit Preview URL

Branch Preview URL
Jun 24 2026, 02:44 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by the DB modular redesign (#279), which closes #128: quiz mastery now routes through apply_graph_update, and mastery is an append-only node_mastery_events table (0023 + the graph/study slices) — no more non-atomic RMW or direct graph_nodes writes. Closing as obsolete. Reopen if not fully covered.

@AndresL230
AndresL230 deleted the fix/128-graph-write-integrity branch June 27, 2026 04:20
Sign up for freeto 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

@AndresL230@Jose-Gael-Cruz-Lopez
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(quiz/graph): route mastery writes through one path + refresh course context (#128) - #242

Closed
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity
Closed

fix(quiz/graph): route mastery writes through one path + refresh course context (#128)#242
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

What & why

Knowledge-graph write-integrity fixes from the backend audit — issue #128 (findings #6/#9/#24).

#6 (HIGH) — quiz bypassed apply_graph_update + left course context stale

submit_quiz wrote graph_nodes directly and re-implemented mastery/event logic, and never called update_course_context, so the shared per-course aggregate went stale after every quiz.

  • Extracted a single sanctioned primitive, graph_service.apply_mastery_event(node, delta, *, reason, event_type, user_id) — clamp → capped mastery_events append (effective delta) → bump times_studied/last_studied_at.
  • Bothsubmit_quizandapply_graph_update's updated_nodes loop now route through it → mastery logic lives in one place (honors the "graph writes go through one path" convention).
  • submit_quiz now backgrounds update_course_context(course_id) so the aggregate refreshes without blocking the response.

#9 (MEDIUM) — non-atomic mastery read-modify-write — deferred

Still a non-atomic RMW, but now on a single code path with an inline NOTE. The DB-side atomic append (Postgres RPC) needs migration-runner plumbing and is a follow-up tied to #195/#197. (Deferred deliberately — see discussion on #128.)

#24 (LOW) — directional edge dedup

Dedup now ignores orientation for symmetric relationship types (related/similar) while keeping directional types (prerequisite/builds_on) distinct.

Security note

This branch is rebased on top of the quiz IDOR fix (b3952f0). apply_mastery_event takes an optional user_id so the write stays owner-scoped — the IDOR defense-in-depth is preserved, not weakened, and apply_graph_update now passes user_id too.

Testing

  • TDD: failing tests first, then implementation.
  • +10 new testsapply_mastery_event (incl. clamping, capped events, effective delta, user_id scoping), edge-orientation dedup, quiz course-context refresh.
  • Full backend suite green (718 passed); ruff check . clean (CI ratchet).

Follow-up

Addresses #128.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Quiz submissions now update course context more efficiently in the background after mastery changes.
    • Mastery updates are now applied via a shared graph primitive, ensuring consistent clamping, tier recalculation, and tracked mastery events.
    • Graph edge creation now deduplicates both orientations for symmetric relationship types.
  • Tests

    • Added unit coverage for mastery update behavior (including event history limits and effective deltas).
    • Expanded coverage for edge deduplication/orientation rules and quiz context refresh behavior.

…se context (#128)
Knowledge-graph write-integrity fixes from the backend audit (#128):
- #6 (HIGH): quiz submit no longer writes graph_nodes directly while
re-implementing mastery/event logic. Quiz scoring and apply_graph_update's
updated_nodes loop now both route through a single sanctioned primitive,
graph_service.apply_mastery_event, and quiz submit backgrounds
update_course_context() so the per-course aggregate no longer goes stale
after a quiz.
- #9 (MEDIUM): deferred per decision. The read-modify-write is still
non-atomic, but now lives on one code path with an inline TODO; the
DB-side atomic append is a follow-up tied to the migration-runner (#197).
- #24 (LOW): edge dedup now ignores orientation for symmetric relationship
types (related/similar) while keeping directional types
(prerequisite/builds_on) distinct.
apply_mastery_event takes an optional user_id so the write stays
owner-scoped, preserving the IDOR defense-in-depth from b3952f0.
Tests: +10 (apply_mastery_event incl. user_id scoping, edge orientation,
quiz course-context refresh). Full backend suite green (718 passed); ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 91369f29-97fd-45f5-94f2-aea152351ab1

📥 Commits

Reviewing files that changed from the base of the PR and between 2088493 and ac25d0e.

📒 Files selected for processing (1)
  • backend/routes/quiz.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/routes/quiz.py

📝 Walkthrough

Walkthrough

Introduces apply_mastery_event in graph_service.py as a centralized primitive for clamped mastery writes, mastery tier recomputation, and capped event recording. apply_graph_update and submit_quiz are refactored to delegate to this helper. submit_quiz additionally switches to an async background course-context refresh. Symmetric edge type deduplication is added to prevent bidirectional duplicate edges. Tests cover all three changes.

Changes

Mastery centralization, edge dedup, and quiz route wiring

Layer / File(s)Summary
apply_mastery_event helper and apply_graph_update refactor
backend/services/graph_service.py
Adds apply_mastery_event that clamps mastery score to [0,1], recomputes mastery tier, increments times_studied/last_studied_at, appends capped mastery events with effective delta, and optionally scopes the DB write by user_id. Refactors apply_graph_update's updated_nodes path to call this helper and source before/after/course_id from its return value.
Symmetric edge deduplication
backend/services/graph_service.py
Adds _SYMMETRIC_RELATIONSHIP_TYPES and extends new_edges insertion to look up the reverse-orientation edge for symmetric types, treating it as an existing duplicate and skipping the insert.
Quiz route: apply_mastery_event and async course context refresh
backend/routes/quiz.py
Updates imports, expands the owner-scoped graph_nodes query to include course_id and concept_name, replaces inline mastery/tier/event logic with a call to apply_mastery_event, and conditionally schedules update_course_context as a background task using the returned course_id.
Tests
backend/tests/test_graph_service.py, backend/tests/test_quiz_routes.py
Adds TestApplyMasteryEvent (clamping, tier, user_id scoping, event cap, effective delta, no context-refresh side effect), TestEdgeDedupOrientation (symmetric skip vs. directional insert), and TestSubmitCourseContextRefresh (context refresh called with course_id when present, skipped when absent).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐇 A rabbit once wrote scores by hand,
Scattering mastery across the land.
Now apply_mastery_event takes the wheel,
Clamped and capped — a tidy deal!
Symmetric edges no longer double,
And context refreshes without trouble.
One helper hops, and all is well! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 28.57% 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 nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main change: consolidating mastery writes through one path and adding course context refresh, which are the primary objectives of the PR (finding #6).
Description check✅ PassedThe PR description covers key requirements: what/why section explaining the fixes, changes made across multiple findings, related issues reference, testing details with test counts, and notes for reviewers about security and follow-up work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/128-graph-write-integrity

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 and usage tips.

Comment threadbackend/routes/quiz.py Fixed
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 20, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontendac25d0eCommit Preview URL

Branch Preview URL
Jun 22 2026, 02:54 AM

@coderabbitaicoderabbitaiBot 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.

🧹 Nitpick comments (2)
backend/routes/quiz.py (1)

441-446: 💤 Low value

Silent exception swallowing may hinder debugging.

The except Exception: pass pattern loses valuable diagnostic information if update_course_context fails. Consider logging at debug or warning level to aid troubleshooting without blocking the response.

This mirrors the pattern in apply_graph_update (lines 598-599), so it's consistent with existing code, but both locations could benefit from minimal logging.

🤖 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 `@backend/routes/quiz.py` around lines 441 - 446, The bare `except Exception:
pass` in the `_refresh_course_ctx` function silently swallows any exceptions
from `update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
backend/services/graph_service.py (1)

418-418: datetime.utcnow() is deprecated in Python 3.12+ (project target).

Refactor to datetime.now(timezone.utc).isoformat() for forward compatibility. Note that this changes the ISO format from 2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00, which may impact downstream consumers. This issue affects 14+ locations across graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py, learn.py, and flashcards.py—coordinate the refactor across all services to ensure consistent timestamp format.

🤖 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 `@backend/services/graph_service.py` at line 418, Replace the deprecated
datetime.utcnow().isoformat() call in the "ts" field (line 418) with
datetime.now(timezone.utc).isoformat() to ensure Python 3.12+ compatibility.
First import timezone from the datetime module at the top of the file. Then
locate all 14+ occurrences of datetime.utcnow() across graph_service.py,
social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py,
learn.py, and flashcards.py and apply the same replacement pattern consistently.
Note that this will change the timestamp format from 2024-01-01T12:00:00 to
2024-01-01T12:00:00+00:00 (with timezone offset), so ensure all downstream
consumers that parse or validate these timestamps are aware of this format
change before deploying.
🤖 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.
Nitpick comments:
In `@backend/routes/quiz.py`:
- Around line 441-446: The bare `except Exception: pass` in the
`_refresh_course_ctx` function silently swallows any exceptions from
`update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
In `@backend/services/graph_service.py`:
- Line 418: Replace the deprecated datetime.utcnow().isoformat() call in the
"ts" field (line 418) with datetime.now(timezone.utc).isoformat() to ensure
Python 3.12+ compatibility. First import timezone from the datetime module at
the top of the file. Then locate all 14+ occurrences of datetime.utcnow() across
graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py,
quiz.py, learn.py, and flashcards.py and apply the same replacement pattern
consistently. Note that this will change the timestamp format from
2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00 (with timezone offset), so
ensure all downstream consumers that parse or validate these timestamps are
aware of this format change before deploying.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e508a01f-98ee-482a-8438-12623e5dba95

📥 Commits

Reviewing files that changed from the base of the PR and between 8c71463 and 2088493.

📒 Files selected for processing (4)
  • backend/routes/quiz.py
  • backend/services/graph_service.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py

…swallowing
The background _refresh_course_ctx task swallowed all exceptions with a bare
pass, hiding aggregation/summary failures. Log via logger.exception so failures
are observable while keeping the submit flow non-blocking.
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging8b19719Commit Preview URL

Branch Preview URL
Jun 24 2026, 02:44 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by the DB modular redesign (#279), which closes #128: quiz mastery now routes through apply_graph_update, and mastery is an append-only node_mastery_events table (0023 + the graph/study slices) — no more non-atomic RMW or direct graph_nodes writes. Closing as obsolete. Reopen if not fully covered.

@AndresL230
AndresL230 deleted the fix/128-graph-write-integrity branch June 27, 2026 04:20
Sign up for freeto 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

@AndresL230@Jose-Gael-Cruz-Lopez
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(quiz/graph): route mastery writes through one path + refresh course context (#128) - #242

Closed
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity
Closed

fix(quiz/graph): route mastery writes through one path + refresh course context (#128)#242
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

What & why

Knowledge-graph write-integrity fixes from the backend audit — issue #128 (findings #6/#9/#24).

#6 (HIGH) — quiz bypassed apply_graph_update + left course context stale

submit_quiz wrote graph_nodes directly and re-implemented mastery/event logic, and never called update_course_context, so the shared per-course aggregate went stale after every quiz.

  • Extracted a single sanctioned primitive, graph_service.apply_mastery_event(node, delta, *, reason, event_type, user_id) — clamp → capped mastery_events append (effective delta) → bump times_studied/last_studied_at.
  • Bothsubmit_quizandapply_graph_update's updated_nodes loop now route through it → mastery logic lives in one place (honors the "graph writes go through one path" convention).
  • submit_quiz now backgrounds update_course_context(course_id) so the aggregate refreshes without blocking the response.

#9 (MEDIUM) — non-atomic mastery read-modify-write — deferred

Still a non-atomic RMW, but now on a single code path with an inline NOTE. The DB-side atomic append (Postgres RPC) needs migration-runner plumbing and is a follow-up tied to #195/#197. (Deferred deliberately — see discussion on #128.)

#24 (LOW) — directional edge dedup

Dedup now ignores orientation for symmetric relationship types (related/similar) while keeping directional types (prerequisite/builds_on) distinct.

Security note

This branch is rebased on top of the quiz IDOR fix (b3952f0). apply_mastery_event takes an optional user_id so the write stays owner-scoped — the IDOR defense-in-depth is preserved, not weakened, and apply_graph_update now passes user_id too.

Testing

  • TDD: failing tests first, then implementation.
  • +10 new testsapply_mastery_event (incl. clamping, capped events, effective delta, user_id scoping), edge-orientation dedup, quiz course-context refresh.
  • Full backend suite green (718 passed); ruff check . clean (CI ratchet).

Follow-up

Addresses #128.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Quiz submissions now update course context more efficiently in the background after mastery changes.
    • Mastery updates are now applied via a shared graph primitive, ensuring consistent clamping, tier recalculation, and tracked mastery events.
    • Graph edge creation now deduplicates both orientations for symmetric relationship types.
  • Tests

    • Added unit coverage for mastery update behavior (including event history limits and effective deltas).
    • Expanded coverage for edge deduplication/orientation rules and quiz context refresh behavior.

…se context (#128)
Knowledge-graph write-integrity fixes from the backend audit (#128):
- #6 (HIGH): quiz submit no longer writes graph_nodes directly while
re-implementing mastery/event logic. Quiz scoring and apply_graph_update's
updated_nodes loop now both route through a single sanctioned primitive,
graph_service.apply_mastery_event, and quiz submit backgrounds
update_course_context() so the per-course aggregate no longer goes stale
after a quiz.
- #9 (MEDIUM): deferred per decision. The read-modify-write is still
non-atomic, but now lives on one code path with an inline TODO; the
DB-side atomic append is a follow-up tied to the migration-runner (#197).
- #24 (LOW): edge dedup now ignores orientation for symmetric relationship
types (related/similar) while keeping directional types
(prerequisite/builds_on) distinct.
apply_mastery_event takes an optional user_id so the write stays
owner-scoped, preserving the IDOR defense-in-depth from b3952f0.
Tests: +10 (apply_mastery_event incl. user_id scoping, edge orientation,
quiz course-context refresh). Full backend suite green (718 passed); ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 91369f29-97fd-45f5-94f2-aea152351ab1

📥 Commits

Reviewing files that changed from the base of the PR and between 2088493 and ac25d0e.

📒 Files selected for processing (1)
  • backend/routes/quiz.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/routes/quiz.py

📝 Walkthrough

Walkthrough

Introduces apply_mastery_event in graph_service.py as a centralized primitive for clamped mastery writes, mastery tier recomputation, and capped event recording. apply_graph_update and submit_quiz are refactored to delegate to this helper. submit_quiz additionally switches to an async background course-context refresh. Symmetric edge type deduplication is added to prevent bidirectional duplicate edges. Tests cover all three changes.

Changes

Mastery centralization, edge dedup, and quiz route wiring

Layer / File(s)Summary
apply_mastery_event helper and apply_graph_update refactor
backend/services/graph_service.py
Adds apply_mastery_event that clamps mastery score to [0,1], recomputes mastery tier, increments times_studied/last_studied_at, appends capped mastery events with effective delta, and optionally scopes the DB write by user_id. Refactors apply_graph_update's updated_nodes path to call this helper and source before/after/course_id from its return value.
Symmetric edge deduplication
backend/services/graph_service.py
Adds _SYMMETRIC_RELATIONSHIP_TYPES and extends new_edges insertion to look up the reverse-orientation edge for symmetric types, treating it as an existing duplicate and skipping the insert.
Quiz route: apply_mastery_event and async course context refresh
backend/routes/quiz.py
Updates imports, expands the owner-scoped graph_nodes query to include course_id and concept_name, replaces inline mastery/tier/event logic with a call to apply_mastery_event, and conditionally schedules update_course_context as a background task using the returned course_id.
Tests
backend/tests/test_graph_service.py, backend/tests/test_quiz_routes.py
Adds TestApplyMasteryEvent (clamping, tier, user_id scoping, event cap, effective delta, no context-refresh side effect), TestEdgeDedupOrientation (symmetric skip vs. directional insert), and TestSubmitCourseContextRefresh (context refresh called with course_id when present, skipped when absent).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐇 A rabbit once wrote scores by hand,
Scattering mastery across the land.
Now apply_mastery_event takes the wheel,
Clamped and capped — a tidy deal!
Symmetric edges no longer double,
And context refreshes without trouble.
One helper hops, and all is well! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 28.57% 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 nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main change: consolidating mastery writes through one path and adding course context refresh, which are the primary objectives of the PR (finding #6).
Description check✅ PassedThe PR description covers key requirements: what/why section explaining the fixes, changes made across multiple findings, related issues reference, testing details with test counts, and notes for reviewers about security and follow-up work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/128-graph-write-integrity

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 and usage tips.

Comment threadbackend/routes/quiz.py Fixed
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 20, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontendac25d0eCommit Preview URL

Branch Preview URL
Jun 22 2026, 02:54 AM

@coderabbitaicoderabbitaiBot 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.

🧹 Nitpick comments (2)
backend/routes/quiz.py (1)

441-446: 💤 Low value

Silent exception swallowing may hinder debugging.

The except Exception: pass pattern loses valuable diagnostic information if update_course_context fails. Consider logging at debug or warning level to aid troubleshooting without blocking the response.

This mirrors the pattern in apply_graph_update (lines 598-599), so it's consistent with existing code, but both locations could benefit from minimal logging.

🤖 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 `@backend/routes/quiz.py` around lines 441 - 446, The bare `except Exception:
pass` in the `_refresh_course_ctx` function silently swallows any exceptions
from `update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
backend/services/graph_service.py (1)

418-418: datetime.utcnow() is deprecated in Python 3.12+ (project target).

Refactor to datetime.now(timezone.utc).isoformat() for forward compatibility. Note that this changes the ISO format from 2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00, which may impact downstream consumers. This issue affects 14+ locations across graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py, learn.py, and flashcards.py—coordinate the refactor across all services to ensure consistent timestamp format.

🤖 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 `@backend/services/graph_service.py` at line 418, Replace the deprecated
datetime.utcnow().isoformat() call in the "ts" field (line 418) with
datetime.now(timezone.utc).isoformat() to ensure Python 3.12+ compatibility.
First import timezone from the datetime module at the top of the file. Then
locate all 14+ occurrences of datetime.utcnow() across graph_service.py,
social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py,
learn.py, and flashcards.py and apply the same replacement pattern consistently.
Note that this will change the timestamp format from 2024-01-01T12:00:00 to
2024-01-01T12:00:00+00:00 (with timezone offset), so ensure all downstream
consumers that parse or validate these timestamps are aware of this format
change before deploying.
🤖 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.
Nitpick comments:
In `@backend/routes/quiz.py`:
- Around line 441-446: The bare `except Exception: pass` in the
`_refresh_course_ctx` function silently swallows any exceptions from
`update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
In `@backend/services/graph_service.py`:
- Line 418: Replace the deprecated datetime.utcnow().isoformat() call in the
"ts" field (line 418) with datetime.now(timezone.utc).isoformat() to ensure
Python 3.12+ compatibility. First import timezone from the datetime module at
the top of the file. Then locate all 14+ occurrences of datetime.utcnow() across
graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py,
quiz.py, learn.py, and flashcards.py and apply the same replacement pattern
consistently. Note that this will change the timestamp format from
2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00 (with timezone offset), so
ensure all downstream consumers that parse or validate these timestamps are
aware of this format change before deploying.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e508a01f-98ee-482a-8438-12623e5dba95

📥 Commits

Reviewing files that changed from the base of the PR and between 8c71463 and 2088493.

📒 Files selected for processing (4)
  • backend/routes/quiz.py
  • backend/services/graph_service.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py

…swallowing
The background _refresh_course_ctx task swallowed all exceptions with a bare
pass, hiding aggregation/summary failures. Log via logger.exception so failures
are observable while keeping the submit flow non-blocking.
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging8b19719Commit Preview URL

Branch Preview URL
Jun 24 2026, 02:44 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by the DB modular redesign (#279), which closes #128: quiz mastery now routes through apply_graph_update, and mastery is an append-only node_mastery_events table (0023 + the graph/study slices) — no more non-atomic RMW or direct graph_nodes writes. Closing as obsolete. Reopen if not fully covered.

@AndresL230
AndresL230 deleted the fix/128-graph-write-integrity branch June 27, 2026 04:20
Sign up for freeto 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

@AndresL230@Jose-Gael-Cruz-Lopez
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(quiz/graph): route mastery writes through one path + refresh course context (#128) - #242

Closed
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity
Closed

fix(quiz/graph): route mastery writes through one path + refresh course context (#128)#242
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

What & why

Knowledge-graph write-integrity fixes from the backend audit — issue #128 (findings #6/#9/#24).

#6 (HIGH) — quiz bypassed apply_graph_update + left course context stale

submit_quiz wrote graph_nodes directly and re-implemented mastery/event logic, and never called update_course_context, so the shared per-course aggregate went stale after every quiz.

  • Extracted a single sanctioned primitive, graph_service.apply_mastery_event(node, delta, *, reason, event_type, user_id) — clamp → capped mastery_events append (effective delta) → bump times_studied/last_studied_at.
  • Bothsubmit_quizandapply_graph_update's updated_nodes loop now route through it → mastery logic lives in one place (honors the "graph writes go through one path" convention).
  • submit_quiz now backgrounds update_course_context(course_id) so the aggregate refreshes without blocking the response.

#9 (MEDIUM) — non-atomic mastery read-modify-write — deferred

Still a non-atomic RMW, but now on a single code path with an inline NOTE. The DB-side atomic append (Postgres RPC) needs migration-runner plumbing and is a follow-up tied to #195/#197. (Deferred deliberately — see discussion on #128.)

#24 (LOW) — directional edge dedup

Dedup now ignores orientation for symmetric relationship types (related/similar) while keeping directional types (prerequisite/builds_on) distinct.

Security note

This branch is rebased on top of the quiz IDOR fix (b3952f0). apply_mastery_event takes an optional user_id so the write stays owner-scoped — the IDOR defense-in-depth is preserved, not weakened, and apply_graph_update now passes user_id too.

Testing

  • TDD: failing tests first, then implementation.
  • +10 new testsapply_mastery_event (incl. clamping, capped events, effective delta, user_id scoping), edge-orientation dedup, quiz course-context refresh.
  • Full backend suite green (718 passed); ruff check . clean (CI ratchet).

Follow-up

Addresses #128.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Quiz submissions now update course context more efficiently in the background after mastery changes.
    • Mastery updates are now applied via a shared graph primitive, ensuring consistent clamping, tier recalculation, and tracked mastery events.
    • Graph edge creation now deduplicates both orientations for symmetric relationship types.
  • Tests

    • Added unit coverage for mastery update behavior (including event history limits and effective deltas).
    • Expanded coverage for edge deduplication/orientation rules and quiz context refresh behavior.

…se context (#128)
Knowledge-graph write-integrity fixes from the backend audit (#128):
- #6 (HIGH): quiz submit no longer writes graph_nodes directly while
re-implementing mastery/event logic. Quiz scoring and apply_graph_update's
updated_nodes loop now both route through a single sanctioned primitive,
graph_service.apply_mastery_event, and quiz submit backgrounds
update_course_context() so the per-course aggregate no longer goes stale
after a quiz.
- #9 (MEDIUM): deferred per decision. The read-modify-write is still
non-atomic, but now lives on one code path with an inline TODO; the
DB-side atomic append is a follow-up tied to the migration-runner (#197).
- #24 (LOW): edge dedup now ignores orientation for symmetric relationship
types (related/similar) while keeping directional types
(prerequisite/builds_on) distinct.
apply_mastery_event takes an optional user_id so the write stays
owner-scoped, preserving the IDOR defense-in-depth from b3952f0.
Tests: +10 (apply_mastery_event incl. user_id scoping, edge orientation,
quiz course-context refresh). Full backend suite green (718 passed); ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 91369f29-97fd-45f5-94f2-aea152351ab1

📥 Commits

Reviewing files that changed from the base of the PR and between 2088493 and ac25d0e.

📒 Files selected for processing (1)
  • backend/routes/quiz.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/routes/quiz.py

📝 Walkthrough

Walkthrough

Introduces apply_mastery_event in graph_service.py as a centralized primitive for clamped mastery writes, mastery tier recomputation, and capped event recording. apply_graph_update and submit_quiz are refactored to delegate to this helper. submit_quiz additionally switches to an async background course-context refresh. Symmetric edge type deduplication is added to prevent bidirectional duplicate edges. Tests cover all three changes.

Changes

Mastery centralization, edge dedup, and quiz route wiring

Layer / File(s)Summary
apply_mastery_event helper and apply_graph_update refactor
backend/services/graph_service.py
Adds apply_mastery_event that clamps mastery score to [0,1], recomputes mastery tier, increments times_studied/last_studied_at, appends capped mastery events with effective delta, and optionally scopes the DB write by user_id. Refactors apply_graph_update's updated_nodes path to call this helper and source before/after/course_id from its return value.
Symmetric edge deduplication
backend/services/graph_service.py
Adds _SYMMETRIC_RELATIONSHIP_TYPES and extends new_edges insertion to look up the reverse-orientation edge for symmetric types, treating it as an existing duplicate and skipping the insert.
Quiz route: apply_mastery_event and async course context refresh
backend/routes/quiz.py
Updates imports, expands the owner-scoped graph_nodes query to include course_id and concept_name, replaces inline mastery/tier/event logic with a call to apply_mastery_event, and conditionally schedules update_course_context as a background task using the returned course_id.
Tests
backend/tests/test_graph_service.py, backend/tests/test_quiz_routes.py
Adds TestApplyMasteryEvent (clamping, tier, user_id scoping, event cap, effective delta, no context-refresh side effect), TestEdgeDedupOrientation (symmetric skip vs. directional insert), and TestSubmitCourseContextRefresh (context refresh called with course_id when present, skipped when absent).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐇 A rabbit once wrote scores by hand,
Scattering mastery across the land.
Now apply_mastery_event takes the wheel,
Clamped and capped — a tidy deal!
Symmetric edges no longer double,
And context refreshes without trouble.
One helper hops, and all is well! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 28.57% 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 nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main change: consolidating mastery writes through one path and adding course context refresh, which are the primary objectives of the PR (finding #6).
Description check✅ PassedThe PR description covers key requirements: what/why section explaining the fixes, changes made across multiple findings, related issues reference, testing details with test counts, and notes for reviewers about security and follow-up work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/128-graph-write-integrity

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 and usage tips.

Comment threadbackend/routes/quiz.py Fixed
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 20, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontendac25d0eCommit Preview URL

Branch Preview URL
Jun 22 2026, 02:54 AM

@coderabbitaicoderabbitaiBot 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.

🧹 Nitpick comments (2)
backend/routes/quiz.py (1)

441-446: 💤 Low value

Silent exception swallowing may hinder debugging.

The except Exception: pass pattern loses valuable diagnostic information if update_course_context fails. Consider logging at debug or warning level to aid troubleshooting without blocking the response.

This mirrors the pattern in apply_graph_update (lines 598-599), so it's consistent with existing code, but both locations could benefit from minimal logging.

🤖 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 `@backend/routes/quiz.py` around lines 441 - 446, The bare `except Exception:
pass` in the `_refresh_course_ctx` function silently swallows any exceptions
from `update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
backend/services/graph_service.py (1)

418-418: datetime.utcnow() is deprecated in Python 3.12+ (project target).

Refactor to datetime.now(timezone.utc).isoformat() for forward compatibility. Note that this changes the ISO format from 2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00, which may impact downstream consumers. This issue affects 14+ locations across graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py, learn.py, and flashcards.py—coordinate the refactor across all services to ensure consistent timestamp format.

🤖 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 `@backend/services/graph_service.py` at line 418, Replace the deprecated
datetime.utcnow().isoformat() call in the "ts" field (line 418) with
datetime.now(timezone.utc).isoformat() to ensure Python 3.12+ compatibility.
First import timezone from the datetime module at the top of the file. Then
locate all 14+ occurrences of datetime.utcnow() across graph_service.py,
social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py,
learn.py, and flashcards.py and apply the same replacement pattern consistently.
Note that this will change the timestamp format from 2024-01-01T12:00:00 to
2024-01-01T12:00:00+00:00 (with timezone offset), so ensure all downstream
consumers that parse or validate these timestamps are aware of this format
change before deploying.
🤖 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.
Nitpick comments:
In `@backend/routes/quiz.py`:
- Around line 441-446: The bare `except Exception: pass` in the
`_refresh_course_ctx` function silently swallows any exceptions from
`update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
In `@backend/services/graph_service.py`:
- Line 418: Replace the deprecated datetime.utcnow().isoformat() call in the
"ts" field (line 418) with datetime.now(timezone.utc).isoformat() to ensure
Python 3.12+ compatibility. First import timezone from the datetime module at
the top of the file. Then locate all 14+ occurrences of datetime.utcnow() across
graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py,
quiz.py, learn.py, and flashcards.py and apply the same replacement pattern
consistently. Note that this will change the timestamp format from
2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00 (with timezone offset), so
ensure all downstream consumers that parse or validate these timestamps are
aware of this format change before deploying.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e508a01f-98ee-482a-8438-12623e5dba95

📥 Commits

Reviewing files that changed from the base of the PR and between 8c71463 and 2088493.

📒 Files selected for processing (4)
  • backend/routes/quiz.py
  • backend/services/graph_service.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py

…swallowing
The background _refresh_course_ctx task swallowed all exceptions with a bare
pass, hiding aggregation/summary failures. Log via logger.exception so failures
are observable while keeping the submit flow non-blocking.
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging8b19719Commit Preview URL

Branch Preview URL
Jun 24 2026, 02:44 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by the DB modular redesign (#279), which closes #128: quiz mastery now routes through apply_graph_update, and mastery is an append-only node_mastery_events table (0023 + the graph/study slices) — no more non-atomic RMW or direct graph_nodes writes. Closing as obsolete. Reopen if not fully covered.

@AndresL230
AndresL230 deleted the fix/128-graph-write-integrity branch June 27, 2026 04:20
Sign up for freeto 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

@AndresL230@Jose-Gael-Cruz-Lopez
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(quiz/graph): route mastery writes through one path + refresh course context (#128) - #242

Closed
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity
Closed

fix(quiz/graph): route mastery writes through one path + refresh course context (#128)#242
AndresL230 wants to merge 3 commits into
mainfrom
fix/128-graph-write-integrity

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

What & why

Knowledge-graph write-integrity fixes from the backend audit — issue #128 (findings #6/#9/#24).

#6 (HIGH) — quiz bypassed apply_graph_update + left course context stale

submit_quiz wrote graph_nodes directly and re-implemented mastery/event logic, and never called update_course_context, so the shared per-course aggregate went stale after every quiz.

  • Extracted a single sanctioned primitive, graph_service.apply_mastery_event(node, delta, *, reason, event_type, user_id) — clamp → capped mastery_events append (effective delta) → bump times_studied/last_studied_at.
  • Bothsubmit_quizandapply_graph_update's updated_nodes loop now route through it → mastery logic lives in one place (honors the "graph writes go through one path" convention).
  • submit_quiz now backgrounds update_course_context(course_id) so the aggregate refreshes without blocking the response.

#9 (MEDIUM) — non-atomic mastery read-modify-write — deferred

Still a non-atomic RMW, but now on a single code path with an inline NOTE. The DB-side atomic append (Postgres RPC) needs migration-runner plumbing and is a follow-up tied to #195/#197. (Deferred deliberately — see discussion on #128.)

#24 (LOW) — directional edge dedup

Dedup now ignores orientation for symmetric relationship types (related/similar) while keeping directional types (prerequisite/builds_on) distinct.

Security note

This branch is rebased on top of the quiz IDOR fix (b3952f0). apply_mastery_event takes an optional user_id so the write stays owner-scoped — the IDOR defense-in-depth is preserved, not weakened, and apply_graph_update now passes user_id too.

Testing

  • TDD: failing tests first, then implementation.
  • +10 new testsapply_mastery_event (incl. clamping, capped events, effective delta, user_id scoping), edge-orientation dedup, quiz course-context refresh.
  • Full backend suite green (718 passed); ruff check . clean (CI ratchet).

Follow-up

Addresses #128.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Quiz submissions now update course context more efficiently in the background after mastery changes.
    • Mastery updates are now applied via a shared graph primitive, ensuring consistent clamping, tier recalculation, and tracked mastery events.
    • Graph edge creation now deduplicates both orientations for symmetric relationship types.
  • Tests

    • Added unit coverage for mastery update behavior (including event history limits and effective deltas).
    • Expanded coverage for edge deduplication/orientation rules and quiz context refresh behavior.

…se context (#128)
Knowledge-graph write-integrity fixes from the backend audit (#128):
- #6 (HIGH): quiz submit no longer writes graph_nodes directly while
re-implementing mastery/event logic. Quiz scoring and apply_graph_update's
updated_nodes loop now both route through a single sanctioned primitive,
graph_service.apply_mastery_event, and quiz submit backgrounds
update_course_context() so the per-course aggregate no longer goes stale
after a quiz.
- #9 (MEDIUM): deferred per decision. The read-modify-write is still
non-atomic, but now lives on one code path with an inline TODO; the
DB-side atomic append is a follow-up tied to the migration-runner (#197).
- #24 (LOW): edge dedup now ignores orientation for symmetric relationship
types (related/similar) while keeping directional types
(prerequisite/builds_on) distinct.
apply_mastery_event takes an optional user_id so the write stays
owner-scoped, preserving the IDOR defense-in-depth from b3952f0.
Tests: +10 (apply_mastery_event incl. user_id scoping, edge orientation,
quiz course-context refresh). Full backend suite green (718 passed); ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 91369f29-97fd-45f5-94f2-aea152351ab1

📥 Commits

Reviewing files that changed from the base of the PR and between 2088493 and ac25d0e.

📒 Files selected for processing (1)
  • backend/routes/quiz.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/routes/quiz.py

📝 Walkthrough

Walkthrough

Introduces apply_mastery_event in graph_service.py as a centralized primitive for clamped mastery writes, mastery tier recomputation, and capped event recording. apply_graph_update and submit_quiz are refactored to delegate to this helper. submit_quiz additionally switches to an async background course-context refresh. Symmetric edge type deduplication is added to prevent bidirectional duplicate edges. Tests cover all three changes.

Changes

Mastery centralization, edge dedup, and quiz route wiring

Layer / File(s)Summary
apply_mastery_event helper and apply_graph_update refactor
backend/services/graph_service.py
Adds apply_mastery_event that clamps mastery score to [0,1], recomputes mastery tier, increments times_studied/last_studied_at, appends capped mastery events with effective delta, and optionally scopes the DB write by user_id. Refactors apply_graph_update's updated_nodes path to call this helper and source before/after/course_id from its return value.
Symmetric edge deduplication
backend/services/graph_service.py
Adds _SYMMETRIC_RELATIONSHIP_TYPES and extends new_edges insertion to look up the reverse-orientation edge for symmetric types, treating it as an existing duplicate and skipping the insert.
Quiz route: apply_mastery_event and async course context refresh
backend/routes/quiz.py
Updates imports, expands the owner-scoped graph_nodes query to include course_id and concept_name, replaces inline mastery/tier/event logic with a call to apply_mastery_event, and conditionally schedules update_course_context as a background task using the returned course_id.
Tests
backend/tests/test_graph_service.py, backend/tests/test_quiz_routes.py
Adds TestApplyMasteryEvent (clamping, tier, user_id scoping, event cap, effective delta, no context-refresh side effect), TestEdgeDedupOrientation (symmetric skip vs. directional insert), and TestSubmitCourseContextRefresh (context refresh called with course_id when present, skipped when absent).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐇 A rabbit once wrote scores by hand,
Scattering mastery across the land.
Now apply_mastery_event takes the wheel,
Clamped and capped — a tidy deal!
Symmetric edges no longer double,
And context refreshes without trouble.
One helper hops, and all is well! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 28.57% 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 nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main change: consolidating mastery writes through one path and adding course context refresh, which are the primary objectives of the PR (finding #6).
Description check✅ PassedThe PR description covers key requirements: what/why section explaining the fixes, changes made across multiple findings, related issues reference, testing details with test counts, and notes for reviewers about security and follow-up work.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/128-graph-write-integrity

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 and usage tips.

Comment threadbackend/routes/quiz.py Fixed
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 20, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontendac25d0eCommit Preview URL

Branch Preview URL
Jun 22 2026, 02:54 AM

@coderabbitaicoderabbitaiBot 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.

🧹 Nitpick comments (2)
backend/routes/quiz.py (1)

441-446: 💤 Low value

Silent exception swallowing may hinder debugging.

The except Exception: pass pattern loses valuable diagnostic information if update_course_context fails. Consider logging at debug or warning level to aid troubleshooting without blocking the response.

This mirrors the pattern in apply_graph_update (lines 598-599), so it's consistent with existing code, but both locations could benefit from minimal logging.

🤖 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 `@backend/routes/quiz.py` around lines 441 - 446, The bare `except Exception:
pass` in the `_refresh_course_ctx` function silently swallows any exceptions
from `update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
backend/services/graph_service.py (1)

418-418: datetime.utcnow() is deprecated in Python 3.12+ (project target).

Refactor to datetime.now(timezone.utc).isoformat() for forward compatibility. Note that this changes the ISO format from 2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00, which may impact downstream consumers. This issue affects 14+ locations across graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py, learn.py, and flashcards.py—coordinate the refactor across all services to ensure consistent timestamp format.

🤖 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 `@backend/services/graph_service.py` at line 418, Replace the deprecated
datetime.utcnow().isoformat() call in the "ts" field (line 418) with
datetime.now(timezone.utc).isoformat() to ensure Python 3.12+ compatibility.
First import timezone from the datetime module at the top of the file. Then
locate all 14+ occurrences of datetime.utcnow() across graph_service.py,
social_cache_service.py, quiz_context_service.py, calendar.py, quiz.py,
learn.py, and flashcards.py and apply the same replacement pattern consistently.
Note that this will change the timestamp format from 2024-01-01T12:00:00 to
2024-01-01T12:00:00+00:00 (with timezone offset), so ensure all downstream
consumers that parse or validate these timestamps are aware of this format
change before deploying.
🤖 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.
Nitpick comments:
In `@backend/routes/quiz.py`:
- Around line 441-446: The bare `except Exception: pass` in the
`_refresh_course_ctx` function silently swallows any exceptions from
`update_course_context`, making debugging difficult. Replace the `pass`
statement with a logging call at debug or warning level that captures the
exception details. Include the exception information in the log message to
provide diagnostic context. Consider also applying the same logging pattern to
the similar exception handling in `apply_graph_update` for consistency across
the codebase.
In `@backend/services/graph_service.py`:
- Line 418: Replace the deprecated datetime.utcnow().isoformat() call in the
"ts" field (line 418) with datetime.now(timezone.utc).isoformat() to ensure
Python 3.12+ compatibility. First import timezone from the datetime module at
the top of the file. Then locate all 14+ occurrences of datetime.utcnow() across
graph_service.py, social_cache_service.py, quiz_context_service.py, calendar.py,
quiz.py, learn.py, and flashcards.py and apply the same replacement pattern
consistently. Note that this will change the timestamp format from
2024-01-01T12:00:00 to 2024-01-01T12:00:00+00:00 (with timezone offset), so
ensure all downstream consumers that parse or validate these timestamps are
aware of this format change before deploying.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e508a01f-98ee-482a-8438-12623e5dba95

📥 Commits

Reviewing files that changed from the base of the PR and between 8c71463 and 2088493.

📒 Files selected for processing (4)
  • backend/routes/quiz.py
  • backend/services/graph_service.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py

…swallowing
The background _refresh_course_ctx task swallowed all exceptions with a bare
pass, hiding aggregation/summary failures. Log via logger.exception so failures
are observable while keeping the submit flow non-blocking.
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging8b19719Commit Preview URL

Branch Preview URL
Jun 24 2026, 02:44 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by the DB modular redesign (#279), which closes #128: quiz mastery now routes through apply_graph_update, and mastery is an append-only node_mastery_events table (0023 + the graph/study slices) — no more non-atomic RMW or direct graph_nodes writes. Closing as obsolete. Reopen if not fully covered.

@AndresL230
AndresL230 deleted the fix/128-graph-write-integrity branch June 27, 2026 04:20
Sign up for freeto 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

@AndresL230@Jose-Gael-Cruz-Lopez