{
  "run_id": "T2.1-W01-20260829T010151Z",
  "overlay_version": "gt-w01-1.0",
  "fixture_base": "gt-core-b1.1",
  "lesson_hour_overlap_rule": "LESSON_HOUR_OVERLAP_FORBIDDEN",
  "pass": true,
  "total": 64,
  "passed": 64,
  "failed": 0,
  "backend_sha": "6033c211fcb079e962d6e48960a6c7f437b216be",
  "frontend_sha": "40467af7aa7f6cd7be7bb5e8936132ba846ba184",
  "ctx_ids": {
    "year_id": 83,
    "term_id": 126,
    "t2_year_id": 84,
    "course_id": 115,
    "subject_id": 111,
    "t2_course_id": 116,
    "t2_subject_id": 112,
    "batch_id": 159,
    "t2_batch_id": 160,
    "profile_id": 8,
    "lh1_id": 88,
    "lh2_id": 89,
    "session_id": 268,
    "session_id_2": 269,
    "t21_year_id": 85,
    "t21_term_ids": [
      129,
      130
    ],
    "t21_batch_b": 161,
    "t21_sid_a": 272,
    "t21_sid_b": 273
  },
  "seed": {
    "profile_t1": 3,
    "profile_t2": 4,
    "faculty_t1_teacher": 9,
    "faculty_t1_homeroom": 10,
    "faculty_t2_teacher": 11
  },
  "cases": [
    {
      "case_id": "W01-AY-CREATE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": "empty or existing years company 20",
      "action": "POST /api/v1/academic/academic-years/save",
      "expected": "persisted year company_id=20 with terms",
      "actual": {
        "resp_success": true,
        "persisted": {
          "name": "W01 AY 2026/2027 10151Z",
          "start_date": "2026-07-01",
          "end_date": "2027-06-30",
          "company_id": 20
        }
      },
      "before_state": {
        "count": 3
      },
      "after_state": {
        "count": 4,
        "id": 83
      },
      "notes": ""
    },
    {
      "case_id": "W01-AY-INVALID-RANGE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create year end<start",
      "expected": "validation failure; no new invalid year",
      "actual": {
        "success": false,
        "error": "validation_error",
        "message": "Tanggal selesai tahun ajaran harus pada atau setelah tanggal mulai."
      },
      "before_state": {
        "count": 4
      },
      "after_state": {
        "count": 4
      },
      "notes": ""
    },
    {
      "case_id": "W01-AY-CROSS-TENANT-LIST",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "list academic years",
      "expected": "GT-T2 year id not visible",
      "actual": {
        "leaked": false,
        "count": 4
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-AY-CROSS-TENANT-MUTATE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "mutate GT-T2 year by id",
      "expected": "denied; name unchanged",
      "actual": {
        "success": false,
        "error": "access_denied",
        "message": "Tahun ajaran berada di luar scope sekolah."
      },
      "before_state": null,
      "after_state": {
        "name": "W01 AY T2 2026/2027 10151Z"
      },
      "notes": ""
    },
    {
      "case_id": "W01-GRADE-CREATE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create grade level",
      "expected": "op.course company 20",
      "actual": {
        "id": 115,
        "error": null,
        "message": null
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-SUBJECT-CREATE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create subject",
      "expected": "persisted subject",
      "actual": {
        "id": 111,
        "message": null
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-BATCH-CREATE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create study group / rombel",
      "expected": "op.batch company 20 with year/course",
      "actual": {
        "id": 159,
        "error": null,
        "message": null
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-BATCH-FOREIGN-COURSE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create batch with GT-T2 course",
      "expected": "denied / validation fail",
      "actual": {
        "success": false,
        "error": "access_denied",
        "message": "Jenjang/kelas berada di luar scope sekolah."
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-TEACHER-ASSIGN-HOMEROOM",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "assign faculty_id on batch",
      "expected": "batch.faculty_id updated (Core; not LMS teaching.assignment)",
      "actual": {
        "faculty_id": 9
      },
      "before_state": null,
      "after_state": null,
      "notes": "op.teaching.assignment classified OPTIONAL/LMS — absent on Golden Core"
    },
    {
      "case_id": "W01-TEACHER-ASSIGN-FOREIGN",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "assign GT-T2 faculty to GT-T1 batch",
      "expected": "denied; faculty unchanged",
      "actual": {
        "success": false,
        "error": "access_denied",
        "message": "Terdapat guru di luar scope sekolah."
      },
      "before_state": null,
      "after_state": {
        "faculty_id": 9
      },
      "notes": ""
    },
    {
      "case_id": "W01-LH-PROFILE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "ensure-default lesson hour profile",
      "expected": "profile id",
      "actual": {
        "profile_id": 8,
        "raw_keys": [
          "item"
        ]
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-LH-CREATE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create lesson hour JP1",
      "expected": "persisted scola.lesson.hours",
      "actual": {
        "id": 88,
        "message": null
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-LH-INVALID-RANGE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create start>=end lesson hour",
      "expected": "rejected",
      "actual": {
        "success": false,
        "error": "validation_error",
        "message": "Jam selesai harus setelah jam mulai."
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-CREATE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "POST sessions/save",
      "expected": "persisted op.session with relations + company",
      "actual": {
        "item": {
          "id": 268,
          "faculty_id": [
            9,
            "GT TeacherA"
          ],
          "subject_id": [
            111,
            "Matematika W01 10151Z"
          ],
          "batch_id": [
            159,
            "7A W01 10151Z"
          ],
          "course_id": [
            115,
            "Kelas 7 10151Z"
          ],
          "classroom_id": false,
          "start_datetime": "2026-08-12 12:30:00",
          "end_datetime": "2026-08-12 13:15:00",
          "lesson_hours_id": [
            88,
            "JP1-10151Z (19:30 - 20:15)"
          ]
        },
        "persisted": {
          "batch_id": 159,
          "subject_id": 111,
          "faculty_id": 9,
          "lesson_hours_id": 88,
          "company_id": 20,
          "start_datetime": "2026-08-12 12:30:00",
          "end_datetime": "2026-08-12 13:15:00"
        },
        "message": null
      },
      "before_state": {
        "count": 0
      },
      "after_state": {
        "count": 1,
        "id": 268
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-TEACHER-CLASS-CONFLICT",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "duplicate overlapping session",
      "expected": "conflict; no extra row",
      "actual": {
        "success": false,
        "error": "conflict",
        "message": "Rombel 7A W01 10151Z sudah memiliki sesi lain pada jam yang sama.\nGuru GT TeacherA sudah mengajar pada jam yang sama."
      },
      "before_state": {
        "count": 1
      },
      "after_state": {
        "count": 1
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-UPDATE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "move session datetime",
      "expected": "persisted new times; ownership kept",
      "actual": {
        "before": {
          "start_datetime": "2026-08-12 12:30:00",
          "end_datetime": "2026-08-12 13:15:00",
          "faculty_id": 9
        },
        "after": {
          "start_datetime": "2026-08-13 12:30:00",
          "end_datetime": "2026-08-13 13:15:00",
          "faculty_id": 9
        }
      },
      "before_state": {
        "start_datetime": "2026-08-12 12:30:00",
        "end_datetime": "2026-08-12 13:15:00",
        "faculty_id": 9
      },
      "after_state": {
        "start_datetime": "2026-08-13 12:30:00",
        "end_datetime": "2026-08-13 13:15:00",
        "faculty_id": 9
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-UPDATE-ATOMIC",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "update into conflict with other session",
      "expected": "denied; original unchanged",
      "actual": {
        "success": false,
        "error": "conflict",
        "message": "Sesi bentrok dengan jadwal yang sama (faculty/waktu)."
      },
      "before_state": {
        "start_datetime": "2026-08-13 12:30:00",
        "end_datetime": "2026-08-13 13:15:00",
        "faculty_id": 9
      },
      "after_state": {
        "start_datetime": "2026-08-13 12:30:00",
        "end_datetime": "2026-08-13 13:15:00",
        "faculty_id": 9
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-FOREIGN-BATCH",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create session with foreign relation",
      "expected": "denied without existence leak; no row",
      "actual": {
        "error": "access_denied",
        "message": "Referensi akademik berada di luar scope sekolah."
      },
      "before_state": {
        "count": 2
      },
      "after_state": {
        "count": 2
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-FOREIGN-SUBJECT",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create session with foreign relation",
      "expected": "denied without existence leak; no row",
      "actual": {
        "error": "access_denied",
        "message": "Referensi akademik berada di luar scope sekolah."
      },
      "before_state": {
        "count": 2
      },
      "after_state": {
        "count": 2
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-FOREIGN-FACULTY",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create session with foreign relation",
      "expected": "denied without existence leak; no row",
      "actual": {
        "error": "access_denied",
        "message": "Referensi akademik berada di luar scope sekolah."
      },
      "before_state": {
        "count": 2
      },
      "after_state": {
        "count": 2
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-TEACHER-MUTATE-DENIED",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "sessions/save",
      "expected": "denied; no mutation",
      "actual": {
        "success": false,
        "error": "capability_denied",
        "message": "Insufficient permissions for this API route.",
        "capability": "academics.schedule.manage",
        "path": "/api/v1/academic/sessions/save"
      },
      "before_state": {
        "count": 2
      },
      "after_state": {
        "count": 2
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-STUDENT-MUTATE-DENIED",
      "status": "PASS",
      "actor": "gt.student.s1",
      "precondition": null,
      "action": "sessions/save",
      "expected": "denied; no mutation",
      "actual": {
        "success": false,
        "error": "access_denied",
        "message": "Anda tidak memiliki izin mengubah jadwal pelajaran."
      },
      "before_state": {
        "count": 2
      },
      "after_state": {
        "count": 2
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-PARENT-MUTATE-DENIED",
      "status": "PASS",
      "actor": "gt.parent.p1",
      "precondition": null,
      "action": "sessions/save",
      "expected": "denied; no mutation",
      "actual": {
        "success": false,
        "error": "capability_denied",
        "message": "Insufficient permissions for this API route.",
        "capability": "academics.schedule.manage",
        "path": "/api/v1/academic/sessions/save"
      },
      "before_state": {
        "count": 2
      },
      "after_state": {
        "count": 2
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-RPC-BYPASS",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "call_kw op.session create",
      "expected": "no unauthorized create",
      "actual": {
        "http": 200,
        "error": true,
        "result": null
      },
      "before_state": {
        "count": 2
      },
      "after_state": {
        "count": 2
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-READ-CONSISTENCY",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "list sessions vs DB",
      "expected": "semantic agreement",
      "actual": {
        "found": {
          "id": 268,
          "faculty_id": [
            9,
            "GT TeacherA"
          ],
          "subject_id": [
            111,
            "Matematika W01 10151Z"
          ],
          "batch_id": [
            159,
            "7A W01 10151Z"
          ],
          "course_id": [
            115,
            "Kelas 7 10151Z"
          ],
          "classroom_id": false,
          "start_datetime": "2026-08-13 12:30:00",
          "end_datetime": "2026-08-13 13:15:00",
          "lesson_hours_id": [
            88,
            "JP1-10151Z (19:30 - 20:15)"
          ]
        },
        "db": {
          "batch_id": 159,
          "subject_id": 111,
          "faculty_id": 9
        }
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-DELETE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "sessions/delete",
      "expected": "row removed",
      "actual": {
        "success": true,
        "data": {
          "deleted": 1
        }
      },
      "before_state": {
        "exists": 1
      },
      "after_state": {
        "exists": 0
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-CONCURRENT",
      "status": "PASS",
      "actor": "gt.school_admin x2",
      "precondition": null,
      "action": "two concurrent identical session creates",
      "expected": "exactly one persisted",
      "actual": {
        "successes": 1,
        "created_ids": [
          270
        ],
        "messages": [
          "Sesi bentrok dengan jadwal yang sama (faculty/waktu).",
          null
        ]
      },
      "before_state": {
        "count": 1
      },
      "after_state": {
        "count": 2
      },
      "notes": ""
    },
    {
      "case_id": "W01-SESSION-IDEMPOTENT-DUP",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "sequential duplicate submit",
      "expected": "rejected; no second row",
      "actual": {
        "success": false,
        "error": "conflict",
        "message": "Rombel 7A W01 10151Z sudah memiliki sesi lain pada jam yang sama.\nGuru GT TeacherA sudah mengajar pada jam yang sama."
      },
      "before_state": {
        "count": 2
      },
      "after_state": {
        "count": 2
      },
      "notes": ""
    },
    {
      "case_id": "W01-TERM-CREATE-VALID",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create year + 2 valid terms",
      "expected": "year + both terms persisted",
      "actual": {
        "year_id": 85,
        "term_count": 2
      },
      "before_state": {
        "year_count": 0
      },
      "after_state": {
        "year_count": 1,
        "term_count": 2
      },
      "notes": ""
    },
    {
      "case_id": "W01-TERM-START-AFTER-END",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create year with invalid nested term(s)",
      "expected": "validation_error; zero new year; zero new terms",
      "actual": {
        "success": false,
        "error": "validation_error",
        "message": "Tanggal selesai semester harus pada atau setelah tanggal mulai."
      },
      "before_state": {
        "year": 0,
        "terms": 0
      },
      "after_state": {
        "year": 0,
        "terms": 0
      },
      "notes": ""
    },
    {
      "case_id": "W01-TERM-BEFORE-YEAR",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create year with invalid nested term(s)",
      "expected": "validation_error; zero new year; zero new terms",
      "actual": {
        "success": false,
        "error": "validation_error",
        "message": "Rentang semester harus berada di dalam tahun ajaran."
      },
      "before_state": {
        "year": 0,
        "terms": 0
      },
      "after_state": {
        "year": 0,
        "terms": 0
      },
      "notes": ""
    },
    {
      "case_id": "W01-TERM-AFTER-YEAR",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create year with invalid nested term(s)",
      "expected": "validation_error; zero new year; zero new terms",
      "actual": {
        "success": false,
        "error": "validation_error",
        "message": "Rentang semester harus berada di dalam tahun ajaran."
      },
      "before_state": {
        "year": 0,
        "terms": 0
      },
      "after_state": {
        "year": 0,
        "terms": 0
      },
      "notes": ""
    },
    {
      "case_id": "W01-TERM-MIXED",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create year with invalid nested term(s)",
      "expected": "validation_error; zero new year; zero new terms",
      "actual": {
        "success": false,
        "error": "validation_error",
        "message": "Rentang semester harus berada di dalam tahun ajaran."
      },
      "before_state": {
        "year": 0,
        "terms": 0
      },
      "after_state": {
        "year": 0,
        "terms": 0
      },
      "notes": ""
    },
    {
      "case_id": "W01-TERM-UPDATE-ATOMIC",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "update year+invalid term with replace_terms",
      "expected": "denied; year and terms unchanged",
      "actual": {
        "success": false,
        "error": "validation_error",
        "message": "Rentang semester harus berada di dalam tahun ajaran."
      },
      "before_state": {
        "year": {
          "name": "T21 AY VALID 10151Z",
          "start_date": "2028-07-01",
          "end_date": "2029-06-30"
        },
        "terms": [
          {
            "name": "S1",
            "term_start_date": "2028-07-01",
            "term_end_date": "2028-12-20"
          },
          {
            "name": "S2",
            "term_start_date": "2029-01-08",
            "term_end_date": "2029-06-30"
          }
        ],
        "term_count": 2
      },
      "after_state": {
        "year": {
          "name": "T21 AY VALID 10151Z",
          "start_date": "2028-07-01",
          "end_date": "2029-06-30"
        },
        "terms": [
          {
            "name": "S1",
            "term_start_date": "2028-07-01",
            "term_end_date": "2028-12-20"
          },
          {
            "name": "S2",
            "term_start_date": "2029-01-08",
            "term_end_date": "2029-06-30"
          }
        ],
        "term_count": 2
      },
      "notes": ""
    },
    {
      "case_id": "W01-TERM-CROSS-TENANT",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "mutate GT-T2 year+terms by id",
      "expected": "denied; foreign year/terms unchanged",
      "actual": {
        "success": false,
        "error": "access_denied",
        "message": "Tahun ajaran berada di luar scope sekolah."
      },
      "before_state": {
        "year": {
          "name": "W01 AY T2 2026/2027 10151Z",
          "company_id": 22
        },
        "term_count": 1
      },
      "after_state": {
        "year": {
          "name": "W01 AY T2 2026/2027 10151Z",
          "company_id": 22
        },
        "term_count": 1
      },
      "notes": ""
    },
    {
      "case_id": "W01-LH-OVERLAP-FORBIDDEN",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create overlapping lesson hour without force_overlap",
      "expected": "rejected; no partial row",
      "actual": {
        "rule": "LESSON_HOUR_OVERLAP_FORBIDDEN",
        "result": {
          "success": false,
          "error": "validation_error",
          "message": "Jam pelajaran bentrok dengan:\n• JP1-10151Z (19:30 - 20:15)\n\nJika Anda yakin ingin melanjutkan, silakan tutup pesan ini dan centang opsi \"Izinkan Bentrok\" di bagian Opsi Lanjutan, lalu simpan kembali."
        }
      },
      "before_state": {
        "count": 0
      },
      "after_state": {
        "count": 0
      },
      "notes": "Source constraint: scola.lesson.hours._check_hours"
    },
    {
      "case_id": "W01-LH-INVALID-NO-PARTIAL",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create start>=end lesson hour",
      "expected": "rejected; no row",
      "actual": {
        "success": false,
        "error": "validation_error",
        "message": "Jam selesai harus setelah jam mulai."
      },
      "before_state": {
        "count": 0
      },
      "after_state": {
        "count": 0
      },
      "notes": ""
    },
    {
      "case_id": "W01-LH-FOREIGN-PROFILE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create lesson hour with GT-T2 profile_id",
      "expected": "denied; no row",
      "actual": {
        "result": {
          "success": false,
          "error": "access_denied",
          "message": "Profil jam pelajaran berada di luar scope sekolah."
        }
      },
      "before_state": {
        "count": 0
      },
      "after_state": {
        "count": 0
      },
      "notes": ""
    },
    {
      "case_id": "W01-READ-SCOPE-SETUP",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "create same-tenant session A/B for teacher scope",
      "expected": "distinct faculty/batch sessions persisted",
      "actual": {
        "sid_a": 272,
        "sid_b": 273,
        "db_a": {
          "faculty_id": 9,
          "batch_id": 159
        },
        "db_b": {
          "faculty_id": 10,
          "batch_id": 161
        }
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-READ-OWN",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "sessions/list",
      "expected": "session A (272) visible",
      "actual": {
        "ids_sample": [
          268,
          270,
          272
        ],
        "has_a": true
      },
      "before_state": null,
      "after_state": {
        "db_a": {
          "faculty_id": 9,
          "batch_id": 159
        }
      },
      "notes": ""
    },
    {
      "case_id": "W01-READ-OTHER",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "sessions/list",
      "expected": "session B (273) hidden",
      "actual": {
        "has_b": false,
        "total": 3
      },
      "before_state": null,
      "after_state": {
        "db_b": {
          "faculty_id": 10,
          "batch_id": 161
        }
      },
      "notes": ""
    },
    {
      "case_id": "W01-READ-OTHER-DIRECT-ID",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "sessions/get other id",
      "expected": "denied/hidden without detail leak",
      "actual": {
        "success": false,
        "error": "access_denied",
        "message": "Sesi tidak ditemukan."
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-READ-OTHER-FILTER",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "sessions/list with foreign faculty/batch filters",
      "expected": "filter does not widen; B still hidden",
      "actual": {
        "ids": [],
        "has_b": false
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-READ-COUNT",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "sessions/count",
      "expected": "count matches scoped list; excludes B",
      "actual": {
        "count": 3,
        "list_total": 3
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-READ-HOMEROOM-OWN-BATCH",
      "status": "PASS",
      "actor": "gt.homeroom.7a",
      "precondition": null,
      "action": "sessions/list as homeroom",
      "expected": "homeroom batch session B visible",
      "actual": {
        "has_b": true,
        "has_a": false
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-READ-HOMEROOM-UNRELATED-HIDDEN",
      "status": "PASS",
      "actor": "gt.homeroom.7a",
      "precondition": null,
      "action": "sessions/list as homeroom",
      "expected": "unrelated faculty/batch session A hidden",
      "actual": {
        "has_a": false
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-RPC-TEACHER-CREATE",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "call_kw op.session.create",
      "expected": "no row created (product API policy)",
      "actual": {
        "http": 200,
        "error": {
          "code": 200,
          "message": "Odoo Server Error",
          "data": {
            "name": "odoo.exceptions.AccessError",
            "debug": "Traceback (most recent call last):\n  File \"/home/scola/odoo/odoo/http.py\", line 1764, in _serve_db\n    return service_model.retrying(self._serve_ir_http, self.env)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/odoo/service/model.py\", line 133, in retrying\n    result = func()\n             ^^^^^^\n  File \"/home/scola/odoo/odoo/http.py\", line 1791, in _serve_ir_http\n    response = self.dispatcher.dispatch(rule.endpoint, args)\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/odoo/http.py\", line 1995, in dispatch\n    result = self.request.registry['ir.http']._dispatch(endpoint)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/custom_addons_scola/gcgscola/scola_core/models/ir_http.py\", line 534, in _dispatch\n    return cls._build_capability_gate_response(\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/custom_addons_scola/gcgscola/scola_core/models/ir_http.py\", line 242, in _build_capability_gate_response\n    raise AccessError(\nodoo.exceptions.AccessError: /web/dataset mutations are out of scope for Scola product actors; use registered /api routes.\n",
            "message": "/web/dataset mutations are out of scope for Scola product actors; use registered /api routes.",
            "arguments": [
              "/web/dataset mutations are out of scope for Scola product actors; use registered /api routes."
            ],
            "context": {}
          }
        },
        "result": null
      },
      "before_state": {
        "count": 4
      },
      "after_state": {
        "count": 4
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-TEACHER-WRITE",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "call_kw op.session.write",
      "expected": "no mutation",
      "actual": {
        "http": 200,
        "error": true
      },
      "before_state": {
        "start_datetime": "2026-09-01 13:30:00",
        "faculty_id": 10
      },
      "after_state": {
        "start_datetime": "2026-09-01 13:30:00",
        "faculty_id": 10
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-TEACHER-UNLINK",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "call_kw op.session.unlink",
      "expected": "row retained",
      "actual": {
        "http": 200,
        "error": true
      },
      "before_state": {
        "exists": 1
      },
      "after_state": {
        "exists": 1
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-STUDENT-CREATE",
      "status": "PASS",
      "actor": "gt.student.s1",
      "precondition": null,
      "action": "call_kw op.session.create",
      "expected": "no row created (product API policy)",
      "actual": {
        "http": 200,
        "error": {
          "code": 200,
          "message": "Odoo Server Error",
          "data": {
            "name": "odoo.exceptions.AccessError",
            "debug": "Traceback (most recent call last):\n  File \"/home/scola/odoo/odoo/http.py\", line 1764, in _serve_db\n    return service_model.retrying(self._serve_ir_http, self.env)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/odoo/service/model.py\", line 133, in retrying\n    result = func()\n             ^^^^^^\n  File \"/home/scola/odoo/odoo/http.py\", line 1791, in _serve_ir_http\n    response = self.dispatcher.dispatch(rule.endpoint, args)\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/odoo/http.py\", line 1995, in dispatch\n    result = self.request.registry['ir.http']._dispatch(endpoint)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/custom_addons_scola/gcgscola/scola_core/models/ir_http.py\", line 534, in _dispatch\n    return cls._build_capability_gate_response(\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/custom_addons_scola/gcgscola/scola_core/models/ir_http.py\", line 242, in _build_capability_gate_response\n    raise AccessError(\nodoo.exceptions.AccessError: /web/dataset mutations are out of scope for Scola product actors; use registered /api routes.\n",
            "message": "/web/dataset mutations are out of scope for Scola product actors; use registered /api routes.",
            "arguments": [
              "/web/dataset mutations are out of scope for Scola product actors; use registered /api routes."
            ],
            "context": {}
          }
        },
        "result": null
      },
      "before_state": {
        "count": 4
      },
      "after_state": {
        "count": 4
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-STUDENT-WRITE",
      "status": "PASS",
      "actor": "gt.student.s1",
      "precondition": null,
      "action": "call_kw op.session.write",
      "expected": "no mutation",
      "actual": {
        "http": 200,
        "error": true
      },
      "before_state": {
        "start_datetime": "2026-09-01 13:30:00",
        "faculty_id": 10
      },
      "after_state": {
        "start_datetime": "2026-09-01 13:30:00",
        "faculty_id": 10
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-STUDENT-UNLINK",
      "status": "PASS",
      "actor": "gt.student.s1",
      "precondition": null,
      "action": "call_kw op.session.unlink",
      "expected": "row retained",
      "actual": {
        "http": 200,
        "error": true
      },
      "before_state": {
        "exists": 1
      },
      "after_state": {
        "exists": 1
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-PARENT-CREATE",
      "status": "PASS",
      "actor": "gt.parent.p1",
      "precondition": null,
      "action": "call_kw op.session.create",
      "expected": "no row created (product API policy)",
      "actual": {
        "http": 200,
        "error": {
          "code": 200,
          "message": "Odoo Server Error",
          "data": {
            "name": "odoo.exceptions.AccessError",
            "debug": "Traceback (most recent call last):\n  File \"/home/scola/odoo/odoo/http.py\", line 1764, in _serve_db\n    return service_model.retrying(self._serve_ir_http, self.env)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/odoo/service/model.py\", line 133, in retrying\n    result = func()\n             ^^^^^^\n  File \"/home/scola/odoo/odoo/http.py\", line 1791, in _serve_ir_http\n    response = self.dispatcher.dispatch(rule.endpoint, args)\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/odoo/http.py\", line 1995, in dispatch\n    result = self.request.registry['ir.http']._dispatch(endpoint)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/custom_addons_scola/gcgscola/scola_core/models/ir_http.py\", line 534, in _dispatch\n    return cls._build_capability_gate_response(\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/custom_addons_scola/gcgscola/scola_core/models/ir_http.py\", line 242, in _build_capability_gate_response\n    raise AccessError(\nodoo.exceptions.AccessError: /web/dataset mutations are out of scope for Scola product actors; use registered /api routes.\n",
            "message": "/web/dataset mutations are out of scope for Scola product actors; use registered /api routes.",
            "arguments": [
              "/web/dataset mutations are out of scope for Scola product actors; use registered /api routes."
            ],
            "context": {}
          }
        },
        "result": null
      },
      "before_state": {
        "count": 4
      },
      "after_state": {
        "count": 4
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-PARENT-WRITE",
      "status": "PASS",
      "actor": "gt.parent.p1",
      "precondition": null,
      "action": "call_kw op.session.write",
      "expected": "no mutation",
      "actual": {
        "http": 200,
        "error": true
      },
      "before_state": {
        "start_datetime": "2026-09-01 13:30:00",
        "faculty_id": 10
      },
      "after_state": {
        "start_datetime": "2026-09-01 13:30:00",
        "faculty_id": 10
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-PARENT-UNLINK",
      "status": "PASS",
      "actor": "gt.parent.p1",
      "precondition": null,
      "action": "call_kw op.session.unlink",
      "expected": "row retained",
      "actual": {
        "http": 200,
        "error": true
      },
      "before_state": {
        "exists": 1
      },
      "after_state": {
        "exists": 1
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-ADMIN-CREATE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "call_kw op.session.create",
      "expected": "no row created (product API policy)",
      "actual": {
        "http": 200,
        "error": {
          "code": 200,
          "message": "Odoo Server Error",
          "data": {
            "name": "odoo.exceptions.AccessError",
            "debug": "Traceback (most recent call last):\n  File \"/home/scola/odoo/odoo/http.py\", line 1764, in _serve_db\n    return service_model.retrying(self._serve_ir_http, self.env)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/odoo/service/model.py\", line 133, in retrying\n    result = func()\n             ^^^^^^\n  File \"/home/scola/odoo/odoo/http.py\", line 1791, in _serve_ir_http\n    response = self.dispatcher.dispatch(rule.endpoint, args)\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/odoo/http.py\", line 1995, in dispatch\n    result = self.request.registry['ir.http']._dispatch(endpoint)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/custom_addons_scola/gcgscola/scola_core/models/ir_http.py\", line 534, in _dispatch\n    return cls._build_capability_gate_response(\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/home/scola/odoo/custom_addons_scola/gcgscola/scola_core/models/ir_http.py\", line 242, in _build_capability_gate_response\n    raise AccessError(\nodoo.exceptions.AccessError: /web/dataset mutations are out of scope for Scola product actors; use registered /api routes.\n",
            "message": "/web/dataset mutations are out of scope for Scola product actors; use registered /api routes.",
            "arguments": [
              "/web/dataset mutations are out of scope for Scola product actors; use registered /api routes."
            ],
            "context": {}
          }
        },
        "result": null
      },
      "before_state": {
        "count": 4
      },
      "after_state": {
        "count": 4
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-ADMIN-WRITE",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "call_kw op.session.write",
      "expected": "no mutation",
      "actual": {
        "http": 200,
        "error": true
      },
      "before_state": {
        "start_datetime": "2026-09-01 13:30:00",
        "faculty_id": 10
      },
      "after_state": {
        "start_datetime": "2026-09-01 13:30:00",
        "faculty_id": 10
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-ADMIN-UNLINK",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "call_kw op.session.unlink",
      "expected": "row retained",
      "actual": {
        "http": 200,
        "error": true
      },
      "before_state": {
        "exists": 1
      },
      "after_state": {
        "exists": 1
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-TEACHER-READ-SEARCH",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "call_kw op.session.search for session B",
      "expected": "denied or empty; B not disclosed",
      "actual": {
        "http": 200,
        "error": true,
        "result": null
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-RPC-TEACHER-READ-SEARCH_READ",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "call_kw op.session.search_read for session B",
      "expected": "denied or empty; B not disclosed",
      "actual": {
        "http": 200,
        "error": true,
        "result": null
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-RPC-TEACHER-READ-READ",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "call_kw op.session.read for session B",
      "expected": "denied or empty; B not disclosed",
      "actual": {
        "http": 200,
        "error": true,
        "result": null
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-RPC-TEACHER-READ-SEARCH_COUNT",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "call_kw op.session.search_count for session B",
      "expected": "denied or empty; B not disclosed",
      "actual": {
        "http": 200,
        "error": true,
        "result": null
      },
      "before_state": null,
      "after_state": null,
      "notes": ""
    },
    {
      "case_id": "W01-RPC-AY-CREATE-TEACHER",
      "status": "PASS",
      "actor": "gt.teacher.assigned",
      "precondition": null,
      "action": "call_kw op.academic.year.create",
      "expected": "no year created",
      "actual": {
        "http": 200,
        "error": true,
        "result": null
      },
      "before_state": {
        "count": 0
      },
      "after_state": {
        "count": 0
      },
      "notes": ""
    },
    {
      "case_id": "W01-RPC-AY-CREATE-SCHOOL_ADMIN",
      "status": "PASS",
      "actor": "gt.school_admin",
      "precondition": null,
      "action": "call_kw op.academic.year.create",
      "expected": "no year created",
      "actual": {
        "http": 200,
        "error": true,
        "result": null
      },
      "before_state": {
        "count": 0
      },
      "after_state": {
        "count": 0
      },
      "notes": ""
    }
  ]
}
