feat: add CompetencyCriterion, the criteria tree's leaf - #7
Open
jesperhodge wants to merge 2 commits into
Open
Conversation
jesperhodge
force-pushed
the
jesperhodge/cbe-641-07-criterion
branch
from
September 9, 2026 21:26
551b226 to
68df7e3
Compare
This was referenced Sep 9, 2026
jesperhodge
force-pushed
the
jesperhodge/cbe-641-07-criterion
branch
from
September 9, 2026 21:48
68df7e3 to
196ca9d
Compare
jesperhodge
force-pushed
the
jesperhodge/cbe-641-07-criterion
branch
2 times, most recently
from
September 10, 2026 19:03
6e5e0b9 to
a28a706
Compare
jesperhodge
added this pull request to stack #11
September 11, 2026 13:17
jesperhodge
force-pushed
the
jesperhodge/cbe-641-07-criterion
branch
from
September 11, 2026 13:27
a28a706 to
e5c161c
Compare
jesperhodge
force-pushed
the
jesperhodge/cbe-641-07-criterion
branch
from
September 11, 2026 14:05
e5c161c to
d84daef
Compare
A leaf points at one ObjectTag, meaning one specific piece of tagged content, and takes its pass rule either from a shared CompetencyRuleProfile or from its own inline override pair. A check constraint enforces ADR-0002 Decision 4's invariant: never both, never neither. The stored rule_profile is not resolved at read time. Decision 4 assigns it at four named write events and stores the result, so a criterion that already resolved to a less specific profile is not silently re-governed when a more specific one appears later. Computing that assignment is authoring-API work and is not here. group and object_tag cascade, per ADR-0002 Decision 7: a leaf means nothing without the group above it or the content association it evaluates. rule_profile is RESTRICT rather than PROTECT. Both refuse a direct profile delete while any criterion is assigned to it, which is what makes a profile archive-only at the ORM layer. They differ once the profile is deleted as part of a larger operation: PROTECT raises for any referencing row it finds in the database, so deleting a CompetencyTaxonomy would fail naming a criterion the same operation was already about to remove, while RESTRICT ignores rows that are themselves being deleted and lets that cascade through. Refs openedx#641 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n test down to this PR This model completes the criteria tree, so its own on_delete values, the transitive tag/taxonomy cascades that only exist once it does, the two RESTRICT-vs-PROTECT payoff/residual scenarios, and the tree-wide integration test all belong here, not in the cross-model part 8 suite they were drafted alongside.
jesperhodge
force-pushed
the
jesperhodge/cbe-641-07-criterion
branch
from
September 11, 2026 14:18
d84daef to
e7d08f7
Compare
jesperhodge
removed this pull request from stack #11
September 11, 2026 14:29
jesperhodge
added this pull request to stack #12
September 11, 2026 14:33
jesperhodge
removed this pull request from stack #12
September 11, 2026 14:34
jesperhodge
added this pull request to stack #13
September 11, 2026 14:34
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.
Part 7 of 7 for issue openedx#641. Based on part 6. Completes the criteria tree: this model's own
on_deletevalues, the tests for them, the transitive and scope-owner cases that only exist once the tree is complete, and the tree-wide integration test all land here. There used to be a separate part 8 collecting every model's delete tests in one place; it added nothing of its own once each model's own tests moved to the PR that declares its foreign keys, so it is closed.cbe-641-01-adrscope_codeamendment (openedx#809)cbe-641-02-deps-and-layering.importlinter(#2)cbe-641-03-taxonomy-overrides-orgtaxonomy_overrides_org,models/package (#3)cbe-641-04-criteria-groupCompetencyCriteriaGroup(#4)cbe-641-05-rule-payloadsRuleType,validate_rule_payload(#5)cbe-641-06-rule-profileCompetencyRuleProfile+ seed (#6)cbe-641-07-criterionCompetencyCriterion(#7)What this does
Adds
CompetencyCriterion, a leaf of the tree. A leaf points at oneObjectTag, meaning one specific piece of tagged content, and takes its pass rule either from a sharedCompetencyRuleProfileor from its own inline override pair. A check constraint enforces ADR-0002 Decision 4's invariant: eitherrule_profileis set and both override fields are null, orrule_profileis null and both overrides are set. Never both, never neither.The stored
rule_profileis never re-resolved at read time. Decision 4 assigns it at four named write events and stores the result. Creating a more specific profile later does not silently re-govern a criterion that already resolved to a less specific one; a test pins that, so a property or manager method that recomputed the foreign key on every read would fail rather than quietly contradicting the ADR. Computing the assignment itself is authoring-API work and is not here.The class is named
CompetencyCriterion, singular, and carries noMeta.db_tableoverride, so the table is Django's defaultopenedx_learning_competencycriterion. ADR-0002's heading, "CompetencyCriterionconcept (CompetencyCriteriadatabase table)", names the domain concept the way every other heading in that ADR does rather than instructing a rename, and no model insrc/overridesdb_tabletoday.Reviewing this PR
test_criterion.pyis ordered schema, then the either-profile-or-overrides invariant and every way to violate it, then override payload validation and the profile that is never re-resolved, then indexes and history.test_criterion_deletion.pyis the sibling module for this model's ownon_deletevalues and everything that only exists once it completes the tree.test_criteria_trees.pyholds the one test that needs a wider, mixed tree to make its point: it deletes a group partway down a realistic tree and asserts the exact surviving row set, which is where a per-foreign-key test cannot help you.The invariant is worth checking closely, because three of its four invalid states reach the database check constraint and raise
IntegrityError, while the fourth,rule_type_overrideset with no payload, is caught earlier bysave()'s payload validation and raisesValidationError. Two similar-looking mistakes therefore fail differently, andtest_setting_a_rule_type_override_without_a_payload_is_rejected_by_savedocuments why.Verifying
By hand, the invariant, which is the one a future API can most easily break:
And that the stored profile is not re-resolved at read time:
Refs openedx#641