Restriction by competency
availability_competencies
Availability condition that restricts activities and course sections until a learner has been rated proficient in a selected competency. It reads global user_competency proficiency, caches it per user in the MUC, and supports backup/restore remapping of competency IDs.
No security, code-quality or compliance defects were found after reading every PHP, JavaScript and test file and checking the relevant core APIs.
- Output handling: the competency short name only reaches learners through
description_format_string(). In core this HTML-escapes the value and wraps it so thatinfo::format_info()runsformat_string()later. The picker escapes names on the server withformat_string()and again in the browser withY.Escape.html(). - Data access: all reads go through core persistents (
competency,course_competency,user_competency). There is no raw SQL, no direct filesystem access, no outbound HTTP and no request parameter handling. - Access control:
allow_add()checks the plugin's ownaddinstancecapability in the edit context. Evaluation fails closed in both directions when the condition can't be answered: competencies disabled, the competency deleted, or not restored. - Compliance: a null privacy provider is correct because the plugin stores no personal data. The application cache entry is invalidated through
competency_evidence_created, which core fires fromapi::add_evidence(), the single path that writesuser_competency.proficiency.
There is good PHPUnit and Behat coverage. Nothing found justifies a lower grade.
availability_competencies is a small availability-condition plugin: two classes, an event observer, a cache definition and a YUI form module. It restricts course items until a learner is proficient in a chosen competency. It targets Moodle 5.3 only and ships with extensive PHPUnit and Behat tests.
The plugin's security is sound. It has no pages, endpoints, raw SQL, file handling or HTTP calls. All data access goes through core competency persistents. Text shown to learners goes through core's delayed format_string() mechanism, which escapes HTML. The form escapes competency names on both the server and the client. The condition is designed to fail closed: if the competency system is disabled, or the competency has been deleted or couldn't be mapped during restore, access is denied whether or not the condition is negated.
The cross-request proficiency cache is correctly invalidated by the competency_evidence_created event. In core, this event is raised from the single function that writes proficiency. A one-hour TTL covers the one case that raises no event: GDPR erasure. No findings were raised. The design notes are listed below for completeness.
Findings
Any competency on the site can be stored as a condition. The picker only offers competencies linked to the course. However, as in core's own availability plugins, the server does not check that a stored competencyid belongs to the course, so an editing teacher who crafts the availability JSON can name any competency on the site. This is intentional: the class docblock says conditions may require competencies from other courses. The exposure is small. Competency short names are readable by every authenticated user by default (moodle/competency:competencyview is granted to the user archetype), and the teacher cannot read a learner's proficiency directly from the condition.
The cache observer runs as an internal observer. db/events.php does not set 'internal' => false, so the cache entry is deleted at the moment the event fires, which may be inside a database transaction that hasn't committed yet. In theory, a concurrent request could re-cache the old proficiency in that window, and the stale value would last for up to the one-hour TTL. The window is very small and the outcome is a delay, not a bypass, so this is noted rather than reported.
The YUI build is current. The built moodle-availability_competencies-form.js and -debug.js match yui/src/form/js/form.js, and the minified build implements the same logic, including the Y.Escape.html() call on competency names.