The classroom's rules, in one file — and a table that checks them - #720
Open
mircealungu wants to merge 3 commits into
Open
The classroom's rules, in one file — and a table that checks them#720mircealungu wants to merge 3 commits into
mircealungu wants to merge 3 commits into
Conversation
The rules for what a student may see are spread across cohort_articles_for_user, _classroom_only and the article filters. No file states them, which is why they keep being got wrong: the language filter that emptied Jack's classroom, the off-language texts 54 students cannot open, and his question about whether two classes would collide were all the same gap in one form or another. Eight rows, each one world and what a student in it sees. Readable in a minute, and executed against the real model rather than a description of it. Writing them down immediately found one: _tiago_exercises dereferenced user.learned_language.code with no guard, and features_for_user runs on every /user_details -- so an account with no learned language would have taken down the whole call. No such account exists in production, and the model allows one; the classroom code already handles it, so the two disagreed. Guarded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The table in the previous commit checks that the rules hold. This makes the
code that implements them say the same thing.
zeeguu/core/classroom.py is the whole of the classroom's behaviour, in the
order someone would ask about it: which classes is this student in, which
texts are in them, which of those can the student read, and is the classroom
the whole app for them. Each rule is one function and the sentence that
justifies it:
student_can_read the student's own language decides, not the class's
classroom_of one merged list; a text in two classes appears once
hidden_from the texts a class holds that nobody in it can open
sees_only_class_texts strictest class wins; teachers are never restricted
They used to be spread across User.cohort_articles_for_user, _classroom_only
and the article filters, with no file stating any of them -- which is how a
Greek learner in an English class ended up staring at a blank app, and how 54
students came to have texts in their class they cannot open.
Both call sites are now adapters. cohort_articles_for_user turns the answer
into article infos; _classroom_only forwards. No behaviour change, and the
table is what says so: 298 tests and 20 subtests pass unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
hidden_from was in the same file as student_can_read, and it is not a rule --
it walks every class and every text and applies one. Mixing the two means the
file is no longer readable in a minute, because you have to sort the policy
from the traversal yourself.
classroom/rules.py now holds only policies: functions that answer "is this
allowed?" from values they are given, with no traversal, no query and no I/O.
Two of them, 44 lines including the prose:
student_can_read(student, text)
app_is_reduced_to_classroom(user, classes)
Note the second takes the classes rather than fetching them -- that is what
keeps it a rule.
classroom/queries.py finds things and applies those: classes_of, texts_of,
classroom_of, hidden_from, sees_only_class_texts. Queries call rules; rules
never call queries.
No behaviour change: 298 tests and 20 subtests pass unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Two commits: a table that checks the rules, and a file that states them.
The problem
The rules for what a student sees were spread across
User.cohort_articles_for_user,_classroom_onlyand the article filters. No file stated any of them. That is the common root of the last fortnight's bugs — the Greek learner staring at a blank app, the 54 students with texts in their class they cannot open, the teacher exemption that made the first bug report misleading.1.
zeeguu/core/classroom.py— the rules, readableThe whole of the classroom's behaviour in the order someone would ask about it. Each rule is one function and the sentence that justifies it:
Both former homes become adapters:
cohort_articles_for_userturns the answer into article infos and tags them;_classroom_onlyforwards.2.
test_classroom_visibility_rules.py— the same rules as a tableA row is one world.
!on a class name means "students see only the texts I share". Each row builds that world and asks the real code.No behaviour change, and the table is what says so
298 tests and 20 subtests pass unchanged. The refactor is exactly the kind that is nerve-racking without a spec and dull with one.
The table also found a live fragility on its first run:
_tiago_exercisesdereferenceduser.learned_language.codewith no guard, andfeatures_for_userruns on every/user_details— an account with no learned language would have taken down the whole call. Zero such accounts exist today;cohort_articles_for_useralready handled the case, so the two halves disagreed. Guarded.