metatroncubeswdev 151bc32747 O7: mc_education_portal - parent/student portal, the live refused-access demo
Serves Demo Scene 5 in full, including the specific requirement to
demonstrate a refused access attempt live on the call. Every route
that takes a student id resolves it through _get_authorized_student(),
which does no manual comparison of its own - it relies on mc.student's
own record rules (a real mc.student.guardian link for guardians,
partner_id for students, never group membership alone, per CLAUDE.md
sec 3's "Guardian access is by relationship, not by role") to filter
a plain search(), and turns an empty result into an explicit
AccessError. The child switcher - "the single most convincing single
feature in the demo" per the spec - is just that same search with no
id filter, returning exactly the caller's own children.

Verified with real HTTP requests (HttpCase), not with_user()
shortcuts alone: authenticated as a real guardian, opened their own
child's dashboard and all four sub-pages (fees/attendance/timetable/
results) and got 200 on every one, then requested another family's
child by id and got 403 with the exact refusal message, on every one
of those same five routes. That is "editing the student identifier in
the URL and showing that it is refused" performed for real inside the
test, not asserted from a domain expression.

That same real-request testing earned its keep twice over, catching
two gaps a with_user()-only test would have missed entirely:
  - mc.batch had never been portal-reachable before this module and
    had no access row at all for guardian/student - a legitimate
    own-child dashboard load failed with a raw ACL error, not even a
    record-rule denial. Added mc.batch rules scoped the same way as
    everything else (via mc.batch.enrollment_ids, a new reverse field
    this module adds).
  - Rendering the timetable page touches mc.subject, mc.room, mc.exam,
    mc.academic.year/term and hr.employee through related-field reads,
    each of which triggers its own ACL check independent of whatever
    rule scopes mc.timetable.slot itself. Granted broad (unrestricted)
    guardian/student read on the non-sensitive reference models
    (subject/room/exam/year/term names - catalog data, not per-student
    data, same reasoning already applied to Teacher/Staff/Accountant
    in earlier modules); used sudo() instead for hr.employee
    specifically, since employee records carry real HR data no
    blanket portal grant should touch, with a comment noting the
    student was already authorized by that point in the request.

Fees needs no new rule at all: mc_education_fees deliberately bills
the primary guardian's own partner_id (a design decision made back in
O3, for exactly this reason), so stock account+portal's own
partner_id-scoped visibility already covers it, and group_guardian/
group_student already imply base.group_portal from O1.

mc.notice is a new model - shared/DOMAIN_MODEL.md lists it as an
entity Track O needs but no module spec in CLAUDE.md sec 5 ever gave
it a field table; added here since it is specifically an O7 view
requirement, scoped by the same batch-or-school-wide pattern as
everything else.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 13:32:38 -04:00

4.5 KiB

1idnamemodel_id:idgroup_id:idperm_readperm_writeperm_createperm_unlink
2access_mc_notice_administratormc.notice.administratormodel_mc_noticemc_education_base.group_school_administrator1111
3access_mc_notice_staffmc.notice.staffmodel_mc_noticemc_education_base.group_school_staff1111
4access_mc_notice_teachermc.notice.teachermodel_mc_noticemc_education_base.group_teacher1000
5access_mc_notice_guardianmc.notice.guardianmodel_mc_noticemc_education_base.group_guardian1000
6access_mc_notice_studentmc.notice.studentmodel_mc_noticemc_education_base.group_student1000
7access_mc_student_guardian_portalmc.student.guardian.portalmc_education_base.model_mc_studentmc_education_base.group_guardian1000
8access_mc_student_student_portalmc.student.student.portalmc_education_base.model_mc_studentmc_education_base.group_student1000
9access_mc_guardian_guardian_portalmc.guardian.guardian.portalmc_education_base.model_mc_guardianmc_education_base.group_guardian1000
10access_mc_guardian_student_portalmc.guardian.student.portalmc_education_base.model_mc_guardianmc_education_base.group_student1000
11access_mc_student_guardian_link_guardian_portalmc.student.guardian.link.guardian.portalmc_education_base.model_mc_student_guardianmc_education_base.group_guardian1000
12access_mc_student_guardian_link_student_portalmc.student.guardian.link.student.portalmc_education_base.model_mc_student_guardianmc_education_base.group_student1000
13access_mc_enrollment_guardian_portalmc.enrollment.guardian.portalmc_education_base.model_mc_enrollmentmc_education_base.group_guardian1000
14access_mc_enrollment_student_portalmc.enrollment.student.portalmc_education_base.model_mc_enrollmentmc_education_base.group_student1000
15access_mc_batch_guardian_portalmc.batch.guardian.portalmc_education_base.model_mc_batchmc_education_base.group_guardian1000
16access_mc_batch_student_portalmc.batch.student.portalmc_education_base.model_mc_batchmc_education_base.group_student1000
17access_mc_attendance_guardian_portalmc.attendance.guardian.portalmc_education_attendance.model_mc_attendancemc_education_base.group_guardian1000
18access_mc_attendance_student_portalmc.attendance.student.portalmc_education_attendance.model_mc_attendancemc_education_base.group_student1000
19access_mc_mark_guardian_portalmc.mark.guardian.portalmc_education_exam.model_mc_markmc_education_base.group_guardian1000
20access_mc_mark_student_portalmc.mark.student.portalmc_education_exam.model_mc_markmc_education_base.group_student1000
21access_mc_timetable_slot_guardian_portalmc.timetable.slot.guardian.portalmc_education_timetable.model_mc_timetable_slotmc_education_base.group_guardian1000
22access_mc_timetable_slot_student_portalmc.timetable.slot.student.portalmc_education_timetable.model_mc_timetable_slotmc_education_base.group_student1000
23access_mc_academic_year_guardian_portalmc.academic.year.guardian.portalmc_education_base.model_mc_academic_yearmc_education_base.group_guardian1000
24access_mc_academic_year_student_portalmc.academic.year.student.portalmc_education_base.model_mc_academic_yearmc_education_base.group_student1000
25access_mc_academic_term_guardian_portalmc.academic.term.guardian.portalmc_education_base.model_mc_academic_termmc_education_base.group_guardian1000
26access_mc_academic_term_student_portalmc.academic.term.student.portalmc_education_base.model_mc_academic_termmc_education_base.group_student1000
27access_mc_program_guardian_portalmc.program.guardian.portalmc_education_base.model_mc_programmc_education_base.group_guardian1000
28access_mc_program_student_portalmc.program.student.portalmc_education_base.model_mc_programmc_education_base.group_student1000
29access_mc_subject_guardian_portalmc.subject.guardian.portalmc_education_base.model_mc_subjectmc_education_base.group_guardian1000
30access_mc_subject_student_portalmc.subject.student.portalmc_education_base.model_mc_subjectmc_education_base.group_student1000
31access_mc_room_guardian_portalmc.room.guardian.portalmc_education_base.model_mc_roommc_education_base.group_guardian1000
32access_mc_room_student_portalmc.room.student.portalmc_education_base.model_mc_roommc_education_base.group_student1000
33access_mc_exam_guardian_portalmc.exam.guardian.portalmc_education_exam.model_mc_exammc_education_base.group_guardian1000
34access_mc_exam_student_portalmc.exam.student.portalmc_education_exam.model_mc_exammc_education_base.group_student1000