Repository navigation
Expand file tree
/
Copy pathassignment-example.html
More file actions
198 lines (186 loc) · 125 KB
/
Copy pathassignment-example.html
File metadata and controls
198 lines (186 loc) · 125 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
<!DOCTYPE html>
<html data-iscarb-standalone="1" data-theme="dark" lang="en"><head><meta charset="utf-8"><meta content="width=device-width,initial-scale=1" name="viewport"><meta content="#006b62" name="theme-color"><meta content="CPIT-455: how the course is graded, and how to do an assignment, step by step, with a complete worked example and a practice build you can run." name="description"><title>How to do an assignment · iSCARB</title><link href="iscarb-hub.css?v=20260928-campus-v3" rel="stylesheet"><link rel="stylesheet" href="course-design.css?v=20260928-campus-v3"><link rel="stylesheet" href="course-shell.css?v=20260928-campus-v3"><script src="course-shell.js?v=20260928-campus-v3" defer></script><script>try{document.documentElement.dataset.theme=localStorage.getItem('iscarb-theme')==='dark'?'dark':'light'}catch(e){document.documentElement.dataset.theme='light'}</script><style>
.ex{border-left:4px solid #0f766e;background:color-mix(in srgb,#0f766e 7%,transparent);padding:12px 16px;border-radius:8px;margin:12px 0}
.ex.weak{border-left-color:#b42318;background:color-mix(in srgb,#b42318 7%,transparent)}
.ex .tag{font-size:12px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;margin:0 0 4px}
.ex p{margin:6px 0}
main pre,main code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
main pre{white-space:pre-wrap;overflow-wrap:anywhere;background:color-mix(in srgb,currentColor 6%,transparent);padding:12px;border-radius:8px;font-size:14px;line-height:1.5}
main table{border-collapse:collapse;width:100%;max-width:100%}main th,main td{border:1px solid color-mix(in srgb,currentColor 20%,transparent);padding:8px;text-align:left;vertical-align:top}
main .tablewrap{overflow-x:auto;max-width:100%}
ol.steps>li{margin:0 0 10px}
#practical{border:1px solid color-mix(in srgb,currentColor 18%,transparent);border-radius:12px;padding:16px;margin:16px 0}
#practical textarea{width:100%;box-sizing:border-box;min-height:110px;padding:10px;border-radius:8px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);background:transparent;color:inherit;font:inherit}
#practical .field-label{display:block;font-weight:800;margin:14px 0 6px}
#practical .buttons{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0}
#practical button{padding:10px 14px;border-radius:8px;border:1px solid color-mix(in srgb,currentColor 30%,transparent);background:transparent;color:inherit;font:inherit;cursor:pointer}
#practical button#buildRun{background:#0f766e;color:#fff;border-color:#0f766e}
#practical button:disabled{opacity:.55;cursor:default}
.sidrow{display:flex;flex-wrap:wrap;gap:8px;align-items:center}.sidrow input{padding:8px;border-radius:8px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);background:transparent;color:inherit;max-width:100%}
</style><!-- visual-guide:css:start --><style>
.vs-strip{list-style:none;margin:12px 0;padding:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
@media (max-width:760px){.vs-strip{grid-template-columns:repeat(2,minmax(0,1fr))}}
h2 .vs-ar-t{display:block;font-weight:600;font-size:.8em;margin-top:4px}
.vs-strip li{position:relative;border:1px solid color-mix(in srgb,currentColor 18%,transparent);border-radius:12px;padding:10px 12px 10px 12px;background:color-mix(in srgb,#0f766e 5%,transparent)}
.vs-strip a{color:inherit;text-decoration:none;display:block}
.vs-strip .vs-n{display:inline-grid;place-items:center;width:26px;height:26px;border-radius:50%;background:#0f766e;color:#fff;font-weight:800;font-size:14px;margin-right:6px}
.vs-strip .vs-i{font-size:20px;vertical-align:middle}
.vs-strip b{display:block;margin-top:6px;font-size:15px}
.vs-strip .vs-en{display:block;font-size:13px;line-height:1.35;margin-top:2px}
.vs-strip .vs-ar{display:block;font-size:14px;line-height:1.5;margin-top:4px;font-weight:600}
.vs-strip .vs-t{position:absolute;top:10px;right:10px;font-size:11px;font-weight:800;opacity:.75}
.vs-fig{margin:18px 0;border:1px solid color-mix(in srgb,currentColor 15%,transparent);border-radius:14px;padding:12px;overflow:hidden}
.vs-fig h3{margin:0 0 4px}.vs-fig .vs-ar-h{display:block;font-size:16px;margin-bottom:8px}
.vs-fig img{display:block;width:100%;height:auto;border-radius:10px;border:1px solid color-mix(in srgb,currentColor 12%,transparent)}
.vs-legend{list-style:none;margin:10px 0 0;padding:0;display:grid;gap:6px}
.vs-legend li{display:grid;grid-template-columns:30px 1fr 1fr;gap:8px;align-items:start}
.vs-legend .vs-n{display:inline-grid;place-items:center;width:26px;height:26px;border-radius:50%;background:#e11d48;color:#fff;font-weight:800;font-size:14px}
.vs-legend [lang=ar]{font-weight:600}
.vs-bb{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin-top:8px}
.vs-bb div{border:2px dashed color-mix(in srgb,currentColor 25%,transparent);border-radius:12px;padding:12px}
@media (max-width:640px){.vs-legend li{grid-template-columns:30px 1fr}.vs-legend li [lang=ar]{grid-column:2}}
</style><!-- visual-guide:css:end --></head><body class="course-page" id="top"><a class="skip" href="#main">Skip to content</a><header class="site-header" data-surface="document"><div class="site-masthead">
<a class="site-brand" href="iscarb.html"><img src="assets/fcit-kau-logo.png" width="48" height="48" alt="FCIT, King Abdulaziz University"><span><strong>iSCARB</strong><small>CPIT-455 · King Abdulaziz University</small></span></a>
<div class="site-utilities"><a class="profile-link" href="https://adeebnoor.github.io/">About & contact <span aria-hidden="true">↗</span></a><button class="theme" id="themeBtn" type="button">Dark mode</button></div>
<button class="nav-toggle" id="navToggle" type="button" aria-controls="courseNav" aria-expanded="false">Menu <span aria-hidden="true">☰</span></button></div>
<nav class="site-nav" id="courseNav" aria-label="Course navigation"><a href="iscarb.html">Learning path</a><a href="student-guide.html" aria-current="page">How to study</a><a href="course-resources.html">Course & sources</a><a href="nelc-alignment.html">Saudi alignment</a><a href="methodology.html">For educators</a><a href="https://qeeem.com/" id="topQeeem" target="_blank" rel="noopener">Evaluation <span aria-hidden="true">↗</span></a></nav></header><main class="wrap" id="main">
<section class="panel"><p class="ey">STUDENT GUIDE · ASSIGNMENTS</p><h1>How to do an assignment, with a complete example</h1><div class="ex" id="start-here"><p class="tag">Start here · ابدأ من هنا</p><ul><li><b>Every assignment:</b> read <a href="#example">3</a>, <a href="#stress">5</a> and <a href="#submit">6</a> (about 10 minutes).</li><li><b>Assignments 3–9:</b> also <a href="#build">4 · the Python build</a>.</li><li><b>Assignments 6–9:</b> also <a href="#new-6-9">7 · three new things</a>.</li><li><b>Stuck on a step?</b> <a href="#steps">2</a> shows it on a picture of the real page.</li></ul><p lang="ar" dir="rtl">كل الواجبات: اقرأ 3 و5 و6. الواجبات 3–9: والقسم 4 (بايثون). الواجبات 6–9: والقسم 7 (الجديد). وإذا تعثرت في خطوة فالقسم 2 يشرحها بالصور.</p></div>
<p class="vs-lead" lang="ar" dir="rtl" style="font-size:1.1rem;font-weight:600">هذه الصفحة تشرح الواجب بالصور: تسع خطوات، ولكل خطوة صورة من الصفحة الحقيقية، ثم مثال محلول على شكل بطاقات.</p><p>The worked example uses a <b>practice case that is not one of your assignments</b>: copy its method, not its content. The practice build runs like the real one and is not assessed.</p>
<p><a href="#grading">1 · How the course is graded</a> · <a href="#steps">2 · The steps, in pictures</a> · <a href="#example">3 · Worked example: Part A</a> · <a href="#build">4 · The Python build</a> · <a href="#stress">5 · New evidence and REFIT</a> · <a href="#submit">6 · Declare and submit</a> · <a href="#new-6-9">7 · New in Assignments 6–9</a> · <a href="#faq">8 · Questions students ask</a></p></section>
<section class="section" id="grading"><h2>1 · How the course is graded · 100 marks</h2>
<div class="tablewrap"><table><thead><tr><th scope="col">Component</th><th scope="col">Marks</th><th scope="col">What it covers</th></tr></thead><tbody>
<tr><td>Assignments 1–9</td><td><b>30</b></td><td>The nine chapter assignments, marked with each assignment's rubric (Assignment 1: 4 points; Assignments 2–9: 5 points each).</td></tr>
<tr><td>Midterm exam</td><td><b>30</b></td><td>The chapters taught before the exam: the five chapter objectives, the classroom concepts and the required source review.</td></tr>
<tr><td>Final exam</td><td><b>30</b></td><td>The whole course, on the same scope.</td></tr>
<tr><td>Participation</td><td><b>10</b></td><td>Coming prepared (the five-objective practice before class) and taking part in class: stations, votes and explain-it questions. Details are announced in Blackboard.</td></tr>
</tbody></table></div><!-- lab-credit:start --><div class="lab-credit" role="note"><p><b>Practical hour.</b> Lab credit is 0. The practical hour is the coded build, the tests, and the recorded run inside Assignments 3–9, completed in the weekly 90-minute practical session. It is marked. It is not optional.</p><p lang="ar" dir="rtl"><b>الساعة التطبيقية.</b> رصيد المعمل صفر. الساعة التطبيقية هي البناء البرمجي والاختبارات والتشغيل المسجَّل داخل الواجبات 3–9، وتُنجز في الحصة التطبيقية الأسبوعية ومدتها 90 دقيقة. وهي مُقيَّمة، وليست اختيارية.</p></div><!-- lab-credit:end --><!-- course-specification:grades --><p>The full course specification, in the ETEC T-104 structure: <a href="course-specification.html">course specification</a>. <span lang="ar" dir="rtl">توصيف المقرر كاملاً: <a href="course-specification.html">توصيف المقرر</a>.</span></p>
<p><b>How the assignment points become 30 marks.</b> The nine assignments give 44 rubric points in total (4 + 8 × 5). Your assignment mark is <b>your points ÷ 44 × 30</b>. Each 5-point assignment is therefore worth about 3.4 marks of the course, so none of them is small.</p>
<div class="ex"><p class="tag">Example</p><p>Points: 4 + 4.5 + 4 + 5 + 3.75 + 4.5 + 5 + 4 + 4.25 = <b>39 of 44</b>. Assignment mark = 39 ÷ 44 × 30 = <b>26.6 of 30</b>.</p></div>
<p>The Python build in Assignments 3–9 is marked inside each assignment's 5 points (the second rubric row). Running the tests is a separate rubric row, TESTS RUN: it is required, and longer writing does not replace it. The micro-viva is a short check of that same work, not a separate exam.</p></section>
<section class="section" id="steps"><h2>2 · The nine steps, in pictures <span class="vs-ar-t" lang="ar" dir="rtl">الخطوات التسع بالصور</span></h2><!-- steps-strip --><!-- visual-guide:steps:start --><p>Each card opens the picture of that step. <span lang="ar" dir="rtl">اضغط على أي خطوة لترى صورتها الحقيقية.</span></p><ol class="vs-strip" aria-label="The assignment in nine steps"><li><a href="assignment-example.html#pic-open"><span class="vs-n">1</span><span class="vs-i" aria-hidden="true">📖</span><span class="vs-t">15 min</span><b>Prepare</b><span class="vs-en">Lecture, the two readings, the five practice questions</span><span class="vs-ar" lang="ar" dir="rtl">احضر المحاضرة، اقرأ المصدرين، وحلّ الأسئلة الخمسة</span></a></li><li><a href="assignment-example.html#pic-open"><span class="vs-n">2</span><span class="vs-i" aria-hidden="true">🔑</span><span class="vs-t">1 min</span><b>Open</b><span class="vs-en">Open it with your Student ID, in the same browser every time</span><span class="vs-ar" lang="ar" dir="rtl">افتح الواجب برقمك الجامعي، ومن نفس المتصفح دائماً</span></a></li><li><a href="assignment-example.html#pic-parta"><span class="vs-n">3</span><span class="vs-i" aria-hidden="true">✍️</span><span class="vs-t">20 min</span><b>Part A</b><span class="vs-en">FIT · artifact · BOUND · ACT + EVIDENCE, 120–180 words</span><span class="vs-ar" lang="ar" dir="rtl">اكتب حكمك الأول: المشكلة، المهمة، الحدود، القرار والدليل</span></a></li><li><a href="assignment-example.html#pic-build"><span class="vs-n">4</span><span class="vs-i" aria-hidden="true">🐍</span><span class="vs-t">30 min</span><b>Build</b><span class="vs-en">Plan, complete the code, write 3+ tests, Run</span><span class="vs-ar" lang="ar" dir="rtl">اكتب الخطة، أكمل الكود، 3 اختبارات على الأقل، ثم Run</span></a></li><li><a href="assignment-example.html#pic-commit"><span class="vs-n">5</span><span class="vs-i" aria-hidden="true">🔒</span><span class="vs-t">1 min</span><b>Commit</b><span class="vs-en">Review & commit Part A: it becomes read-only</span><span class="vs-ar" lang="ar" dir="rtl">ثبّت الجزء A: يصبح للقراءة فقط</span></a></li><li><a href="assignment-example.html#pic-stress"><span class="vs-n">6</span><span class="vs-i" aria-hidden="true">📋</span><span class="vs-t">2 min</span><b>New evidence</b><span class="vs-en">It opens on the page right after you commit. Read it</span><span class="vs-ar" lang="ar" dir="rtl">يظهر الدليل الجديد في الصفحة مباشرة بعد التثبيت: اقرأه</span></a></li><li><a href="assignment-example.html#pic-refit"><span class="vs-n">7</span><span class="vs-i" aria-hidden="true">🔄</span><span class="vs-t">10 min</span><b>Part B</b><span class="vs-en">Boundary status, then RETAIN / REVISE / REPLACE and why</span><span class="vs-ar" lang="ar" dir="rtl">هل تغيّر قرارك؟ اختر وبرّر، ثم اكتب القرار النهائي</span></a></li><li><a href="assignment-example.html#pic-submit"><span class="vs-n">8</span><span class="vs-i" aria-hidden="true">🖊️</span><span class="vs-t">2 min</span><b>Sign</b><span class="vs-en">Declare AI use, type your name, tick the box</span><span class="vs-ar" lang="ar" dir="rtl">صرّح بالذكاء الاصطناعي، اكتب اسمك، وضع العلامة</span></a></li><li><a href="assignment-example.html#pic-pdf"><span class="vs-n">9</span><span class="vs-i" aria-hidden="true">✅</span><span class="vs-t">3 min</span><b>Submit</b><span class="vs-en">Print / PDF, then upload the PDF in Blackboard</span><span class="vs-ar" lang="ar" dir="rtl">صدّر PDF وارفعه في Blackboard</span></a></li></ol><!-- visual-guide:steps:end --><!-- steps-pictures --><details class="faq" id="pictures"><summary><b>Show the pictures of each step</b> <span lang="ar" dir="rtl">اعرض صور الخطوات</span></summary><!-- visual-guide:pictures:start --><figure class="vs-fig" id="pic-open"><h3>Step 2 · Open the assignment</h3><span class="vs-ar-h" lang="ar" dir="rtl">الخطوة 2 · افتح الواجب</span><img src="assets/guide/step-open.webp" width="980" height="276" loading="lazy" alt="Step 2 · Open the assignment: screenshot of the assignment page with numbered markers. 1: Type your Student ID; 2: Tick the pledge; 3: Open assignment"><ol class="vs-legend"><li><span class="vs-n">1</span><span>Type your Student ID</span><span lang="ar" dir="rtl">اكتب رقمك الجامعي</span></li><li><span class="vs-n">2</span><span>Tick the pledge</span><span lang="ar" dir="rtl">ضع علامة على التعهد</span></li><li><span class="vs-n">3</span><span>Open assignment</span><span lang="ar" dir="rtl">اضغط Open assignment</span></li></ol></figure><figure class="vs-fig" id="pic-parta"><h3>Step 3 · Part A: read the case, then FIT</h3><span class="vs-ar-h" lang="ar" dir="rtl">الخطوة 3 · الجزء A: اقرأ الحالة، ثم FIT</span><img src="assets/guide/step-parta-1.webp" width="1074" height="760" loading="lazy" alt="Step 3 · Part A: read the case, then FIT: screenshot of the assignment page with numbered markers. 1: Read the case: only these facts count; 2: FIT: the problem that decides the case"><ol class="vs-legend"><li><span class="vs-n">1</span><span>Read the case: only these facts count</span><span lang="ar" dir="rtl">اقرأ الحالة: الحقائق المكتوبة فقط هي المعتمدة</span></li><li><span class="vs-n">2</span><span>FIT: the problem that decides the case</span><span lang="ar" dir="rtl">FIT: ما المشكلة التي تحكم القرار؟</span></li></ol></figure><figure class="vs-fig" id="pic-parta-2"><h3>Step 3 · Part A: the other four fields</h3><span class="vs-ar-h" lang="ar" dir="rtl">الخطوة 3 · الجزء A: بقية الحقول</span><img src="assets/guide/step-parta-2.webp" width="1074" height="1100" loading="lazy" alt="Step 3 · Part A: the other four fields: screenshot of the assignment page with numbered markers. 3: The chapter artifact (TRACE, TEST, …); 4: BOUND: when your claim stops holding; 5: ACT: what to do now, and who owns it; 6: EVIDENCE: what someone else can check"><ol class="vs-legend"><li><span class="vs-n">3</span><span>The chapter artifact (TRACE, TEST, …)</span><span lang="ar" dir="rtl">مهمة الفصل (TRACE أو TEST …)</span></li><li><span class="vs-n">4</span><span>BOUND: when your claim stops holding</span><span lang="ar" dir="rtl">BOUND: متى يتوقف حكمك عن الصحة؟</span></li><li><span class="vs-n">5</span><span>ACT: what to do now, and who owns it</span><span lang="ar" dir="rtl">ACT: ماذا تفعل الآن ومن المسؤول؟</span></li><li><span class="vs-n">6</span><span>EVIDENCE: what someone else can check</span><span lang="ar" dir="rtl">EVIDENCE: ما الدليل الذي يستطيع غيرك فحصه؟</span></li></ol></figure><figure class="vs-fig" id="pic-build"><h3>Step 4 · The Python build</h3><span class="vs-ar-h" lang="ar" dir="rtl">الخطوة 4 · البناء البرمجي</span><img src="assets/guide/step-build.webp" width="1074" height="728" loading="lazy" alt="Step 4 · The Python build: screenshot of the assignment page with numbered markers. 1: Write a short plan first; 2: Complete the code and write 3+ tests; 3: Run my tests and the course checks"><ol class="vs-legend"><li><span class="vs-n">1</span><span>Write a short plan first</span><span lang="ar" dir="rtl">اكتب خطة قصيرة أولاً</span></li><li><span class="vs-n">2</span><span>Complete the code and write 3+ tests</span><span lang="ar" dir="rtl">أكمل الكود واكتب 3 اختبارات على الأقل</span></li><li><span class="vs-n">3</span><span>Run my tests and the course checks</span><span lang="ar" dir="rtl">اضغط Run لتشغيل الاختبارات</span></li></ol></figure><figure class="vs-fig" id="pic-record"><h3>Step 4 · Read your build record</h3><span class="vs-ar-h" lang="ar" dir="rtl">الخطوة 4 · اقرأ سجل البناء</span><img src="assets/guide/step-record.webp" width="1070" height="587" loading="lazy" alt="Step 4 · Read your build record: screenshot of the assignment page with numbered markers. 1: Your micro-viva change request; 2: Your own tests: ✗ means fix code or test; 3: Course checks: the rule from the task; 4: “Not caught”: add a test for this bug"><ol class="vs-legend"><li><span class="vs-n">1</span><span>Your micro-viva change request</span><span lang="ar" dir="rtl">طلب التعديل الخاص بك للمقابلة القصيرة</span></li><li><span class="vs-n">2</span><span>Your own tests: ✗ means fix code or test</span><span lang="ar" dir="rtl">اختباراتك: ✗ تعني أصلح الكود أو الاختبار</span></li><li><span class="vs-n">3</span><span>Course checks: the rule from the task</span><span lang="ar" dir="rtl">فحوصات المقرر: قواعد الواجب</span></li><li><span class="vs-n">4</span><span>“Not caught”: add a test for this bug</span><span lang="ar" dir="rtl">«not caught»: أضف اختباراً يكشف هذا الخطأ</span></li></ol></figure><figure class="vs-fig" id="pic-commit"><h3>Step 5 · Commit Part A</h3><span class="vs-ar-h" lang="ar" dir="rtl">الخطوة 5 · ثبّت الجزء A</span><img src="assets/guide/step-commit.webp" width="920" height="130" loading="lazy" alt="Step 5 · Commit Part A: screenshot of the assignment page with numbered markers. 1: Review & commit Part A: freezes Part A, it is not submission"><ol class="vs-legend"><li><span class="vs-n">1</span><span>Review & commit Part A: freezes Part A, it is not submission</span><span lang="ar" dir="rtl">Review & commit يثبّت الجزء A، وهو ليس تسليماً</span></li></ol></figure><figure class="vs-fig" id="pic-stress"><h3>Step 6 · The new evidence opens on the page</h3><span class="vs-ar-h" lang="ar" dir="rtl">الخطوة 6 · الدليل الجديد يظهر في الصفحة</span><img src="assets/guide/step-stress.webp" width="1100" height="415" loading="lazy" alt="Step 6 · The new evidence opens on the page: screenshot of the assignment page with numbered markers. 1: Read the new evidence: it appears right after you commit; 2: Part B starts here, on the same page"><ol class="vs-legend"><li><span class="vs-n">1</span><span>Read the new evidence: it appears right after you commit</span><span lang="ar" dir="rtl">اقرأ الدليل الجديد: يظهر مباشرة بعد التثبيت</span></li><li><span class="vs-n">2</span><span>Part B starts here, on the same page</span><span lang="ar" dir="rtl">الجزء B يبدأ هنا في نفس الصفحة</span></li></ol></figure><figure class="vs-fig" id="pic-refit"><h3>Step 7 · Part B: REFIT</h3><span class="vs-ar-h" lang="ar" dir="rtl">الخطوة 7 · الجزء B: المراجعة</span><img src="assets/guide/step-refit.webp" width="1094" height="819" loading="lazy" alt="Step 7 · Part B: REFIT: screenshot of the assignment page with numbered markers. 1: What happened to your boundary?; 2: RETAIN, REVISE or REPLACE; 3: Defend the choice from the new evidence; 4: The final decision another person can act on"><ol class="vs-legend"><li><span class="vs-n">1</span><span>What happened to your boundary?</span><span lang="ar" dir="rtl">ماذا حدث لحدود حكمك؟</span></li><li><span class="vs-n">2</span><span>RETAIN, REVISE or REPLACE</span><span lang="ar" dir="rtl">أبقِ، عدّل، أو استبدل القرار</span></li><li><span class="vs-n">3</span><span>Defend the choice from the new evidence</span><span lang="ar" dir="rtl">برّر اختيارك من الدليل الجديد</span></li><li><span class="vs-n">4</span><span>The final decision another person can act on</span><span lang="ar" dir="rtl">القرار النهائي الذي ينفّذه غيرك</span></li></ol></figure><figure class="vs-fig" id="pic-submit"><h3>Step 8 · Declare AI and sign</h3><span class="vs-ar-h" lang="ar" dir="rtl">الخطوة 8 · التصريح والتوقيع</span><img src="assets/guide/step-submit.webp" width="1094" height="347" loading="lazy" alt="Step 8 · Declare AI and sign: screenshot of the assignment page with numbered markers. 1: AI use, or “No AI used.”; 2: Type your name; 3: Tick: you reviewed it and take responsibility"><ol class="vs-legend"><li><span class="vs-n">1</span><span>AI use, or “No AI used.”</span><span lang="ar" dir="rtl">استخدام الذكاء الاصطناعي، أو «No AI used.»</span></li><li><span class="vs-n">2</span><span>Type your name</span><span lang="ar" dir="rtl">اكتب اسمك</span></li><li><span class="vs-n">3</span><span>Tick: you reviewed it and take responsibility</span><span lang="ar" dir="rtl">ضع علامة: راجعت العمل وأتحمّل مسؤوليته</span></li></ol></figure><figure class="vs-fig" id="pic-pdf"><h3>Step 9 · Export and submit</h3><span class="vs-ar-h" lang="ar" dir="rtl">الخطوة 9 · صدّر وسلّم</span><img src="assets/guide/step-pdf.webp" width="642" height="130" loading="lazy" alt="Step 9 · Export and submit: screenshot of the assignment page with numbered markers. 4: Print / PDF, check the file, upload it in Blackboard"><ol class="vs-legend"><li><span class="vs-n">4</span><span>Print / PDF, check the file, upload it in Blackboard</span><span lang="ar" dir="rtl">Print / PDF، راجع الملف، ثم ارفعه في Blackboard</span></li></ol></figure><figure class="vs-fig" id="pic-blackboard"><h3>Step 9 · In Blackboard: one item</h3><span class="vs-ar-h" lang="ar" dir="rtl">الخطوة 9 · في Blackboard: خانة واحدة فقط</span><div class="vs-bb"><div><b>Assignment N · Chapter X</b><br>Upload your final PDF here, before the deadline. That is all.<br><span lang="ar" dir="rtl">ارفع ملف PDF النهائي هنا قبل الموعد. هذا كل شيء.</span></div></div></figure><!-- visual-guide:pictures:end --></details><details><summary>Text version of the steps · <span lang="ar" dir="rtl">النسخة النصية</span></summary><ol class="steps">
<li><b>Prepare (15 min).</b> Attend or open the lecture, read the two named source selections, and answer the five practice questions.</li>
<li><b>Open the assignment from the learning path</b> with your Student ID. Always use the same browser and the same ID: drafts are saved on your device only.</li>
<li><b>Part A, your first judgment (20 min, 120–180 words).</b> Answer the fields: the problem (FIT), the chapter artifact, BOUND, ACT and EVIDENCE, the source concept and the transfer question.</li>
<li><b>Python build (30 min).</b> Write a short plan, complete the starter code, write at least three tests, and select <i>Run my tests and the course checks</i>.</li>
<li><b>Review & commit Part A.</b> Part A becomes read-only. This is <b>not</b> submission.</li>
<li><b>Read the new evidence.</b> It opens on the page right after you commit, and Part B starts below it.</li>
<li><b>Part B, REFIT (10 min).</b> Choose the boundary status (INTACT, PRESSURED or CROSSED), choose RETAIN, REVISE or REPLACE, justify it, and write the final decision record.</li>
<li><b>Declare AI use and sign.</b> Say which AI you used and what you checked, or write “No AI used.”, then type your name and tick the box.</li><li><b>Export and submit.</b> Use <i>Print / PDF</i>, check the file, and upload it to the assignment’s single item in Blackboard before the deadline. That is the only upload.</li>
</ol></details></section>
<section class="section" id="example"><h2>3 · Worked example: Part A <span class="vs-ar-t" lang="ar" dir="rtl">مثال محلول</span></h2><div class="ex"><p class="tag">Practice case · fictional, not assessed</p><p>A faculty lab lends cameras and sensor kits. The proposed rule: a student may hold at most two items, and may not borrow while any item is overdue. A new self-service kiosk passed tests in which students borrowed one item. The overdue flag is copied from the library system every night at 02:00. No test tried a third item or an overdue student. You advise the lab manager: open the kiosk, open it with restrictions, or hold.</p></div><p>Each card: <b>✓ a strong answer</b> and <b>✗ a weak one</b> for the same field. <span lang="ar" dir="rtl">في كل بطاقة: ✓ إجابة قوية، و✗ إجابة ضعيفة تخسر الدرجة.</span> The whole strong Part A is about 160 words.</p><style>.wx-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:12px;margin-top:12px}.wx{border:1px solid color-mix(in srgb,currentColor 18%,transparent);border-radius:12px;padding:12px}.wx-h{margin:0 0 8px}.wx-h span{display:block;font-weight:600;margin-top:2px}.wx-good{margin:0 0 8px;padding:8px 10px;border-radius:8px;background:color-mix(in srgb,#15803d 10%,transparent)}.wx-bad{margin:0;padding:8px 10px;border-radius:8px;background:color-mix(in srgb,#b42318 9%,transparent)}.wx-good>span{color:#15803d;font-weight:800}.wx-bad>span{color:#b42318;font-weight:800}</style><div class="wx-grid"><div class="wx"><p class="wx-h"><b>FIT</b> <span lang="ar" dir="rtl">ما المشكلة التي تحكم القرار؟</span></p><p class="wx-good"><span aria-hidden="true">✓</span> The decision depends on whether the kiosk enforces the loan rule on <i>every</i> request. The passed tests show only the allowed path, so they cannot show that a third item or an overdue student is refused.</p><p class="wx-bad"><span aria-hidden="true">✗</span> Security is very important.</p></div><div class="wx"><p class="wx-h"><b>Artifact (here TEST)</b> <span lang="ar" dir="rtl">مهمة الفصل: هنا كتابة الاختبارات</span></p><p class="wx-good"><span aria-hidden="true">✓</span> <b>T1</b> 0 items, nothing overdue → lent. <b>T2</b> holds 2, asks for a third → refused, count stays 2. <b>T3</b> overdue → refused. All three ran in my build; on the real kiosk they are <b>proposed</b>.</p><p class="wx-bad"><span aria-hidden="true">✗</span> Test the system thoroughly.</p></div><div class="wx"><p class="wx-h"><b>BOUND</b> <span lang="ar" dir="rtl">متى يتوقف حكمك عن الصحة؟</span></p><p class="wx-good"><span aria-hidden="true">✓</span> Holds while the overdue flag is at most 24 hours old and the kiosk is the only way to borrow. Stops if the copy timestamp is older than 24 hours, or staff lend at the counter.</p><p class="wx-bad"><span aria-hidden="true">✗</span> If problems occur.</p></div><div class="wx"><p class="wx-h"><b>ACT</b> <span lang="ar" dir="rtl">ماذا تفعل الآن، ومن المسؤول؟</span></p><p class="wx-good"><span aria-hidden="true">✓</span> <b>Proposed:</b> open with one item per student until T2 and T3 pass on the real kiosk. Owner: lab manager; reviewer: faculty IT.</p><p class="wx-bad"><span aria-hidden="true">✗</span> Release after testing.</p></div><div class="wx"><p class="wx-h"><b>EVIDENCE</b> <span lang="ar" dir="rtl">ما الدليل الذي يستطيع غيرك فحصه؟</span></p><p class="wx-good"><span aria-hidden="true">✓</span> My build refuses the third item and the overdue student (all course checks pass, 3 of 3 known bugs caught). <b>Still to verify:</b> the real kiosk and the age of the flag.</p><p class="wx-bad"><span aria-hidden="true">✗</span> The tests passed.</p></div><div class="wx"><p class="wx-h"><b>Source + transfer</b> <span lang="ar" dir="rtl">مفهوم من القراءة، وتطبيق مفهوم آخر</span></p><p class="wx-good"><span aria-hidden="true">✓</span> Slide [from <i>your</i> assigned reading] · fail-safe defaults · my rule refuses any record it cannot read. Transfer: if the library copy fails, refuse loans that need it.</p><p class="wx-bad"><span aria-hidden="true">✗</span> The textbook says safety matters.</p></div></div><p class="small">Weak answers lose credit because nothing in them is specific to the case, nothing can be checked, and the boundary cannot be observed. <span lang="ar" dir="rtl">الإجابة الضعيفة عامة، ولا يمكن فحصها، وحدودها غير قابلة للملاحظة.</span></p></section>
<section class="section" id="build"><h2>4 · The Python build: try it here</h2>
<p>This practice build works exactly like the one in Assignments 3–9. Try the starter first and read the failures, then complete it. The full example solution is below the build.</p>
<div class="sidrow"><label for="sid"><b>Student ID</b> (optional here: it chooses your micro-viva change request)</label><input id="sid" autocomplete="off" placeholder="e.g. 2412345"></div>
<!--practice-build--><section class="card" id="practical" data-build="v2"><p class="ey">PYTHON BUILD · PRACTICE, NOT ASSESSED</p><h2>Practice build: the loan rule</h2><p>Write can_borrow(student, item) for the lab-equipment desk, and tests that try to break the rule. Not assessed: use it to learn the build before your first assignment.</p><p>Budget about 30 minutes. Python runs in your browser: nothing to install, and nothing is uploaded. The first run downloads Python once (about 10 MB, internet needed). Passing every check is necessary, not sufficient: your written record must use what the build shows.</p><p>Your own tests are tested too: the course runs them against known buggy versions of this code, and each bug must make one of your tests fail. Your build record names a <b>micro-viva change request</b> chosen from your Student ID. Be ready to make that change in front of your instructor and explain which tests change and why.</p><p class="hint"><b>Primary source · about 15 minutes:</b> J. H. Saltzer and M. D. Schroeder, “The Protection of Information in Computer Systems,” <i>Proceedings of the IEEE</i> 63(9), 1975. Read it for the principle “fail-safe defaults”. Which line of can_borrow refuses when it cannot read the input? Use the answer in your build plan.</p><details><summary>The course checks your build must pass</summary><ul><li>a normal loan is allowed</li><li>a student at the limit is refused</li><li>a student with an overdue item is refused</li><li>an item that is out is refused</li><li>malformed input is refused without crashing</li><li>your own tests: at least three, all passing</li><li>your tests catch every known bug (mutation test)</li></ul></details><label for="labPrediction" class="field-label">Build plan: what will your code do, and which cases will your tests cover?</label><textarea id="labPrediction" placeholder="My approach, the edge cases I will test, and the case that must be refused or fail safely…"></textarea><label for="labCases" class="field-label">Your Python code and tests</label><p class="hint">Keep the given names. Write at least three functions whose names start with test_. Tab inserts four spaces.</p><textarea id="labCases" spellcheck="false" autocapitalize="off" autocomplete="off" style="font:15px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;tab-size:4;min-height:380px;white-space:pre;overflow-wrap:normal;overflow-x:auto"></textarea><div class="buttons"><button type="button" id="buildRun" data-lab-mode="build">Run my tests and the course checks</button><button type="button" id="buildReset" data-lab-mode="reset">Reset to starter code</button></div><p id="labStatus" role="status" aria-live="polite">Write your plan, then run.</p><pre id="buildOutput" style="white-space:pre-wrap;overflow-wrap:anywhere;font:15px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;tab-size:4"></pre><label for="labEvidence" class="field-label">Build record</label><textarea id="labEvidence" readonly style="min-height:160px" placeholder="Created when you run. Changing the code clears it until you run again."></textarea><div class="coverage-step" id="coverageStep"><p class="ey">COVERAGE STEP · COURSE OUTCOME 11 · DESIGN AND IMPLEMENT TEST CASES ACROSS TESTING STRATEGIES · PRACTICE</p><p>The build record measures which lines and branches of your code your own tests run. The course checks are not counted.</p><ol><li>Make your three tests pass and read the coverage lines in the build record.</li><li>Change one input in one of your tests. Run again: the record shows <i>Run 2</i>.</li><li>Write one sentence: which branch is still not tested, and which input would test it.</li></ol><label for="coverageNote" class="field-label">Coverage sentence</label><textarea id="coverageNote" rows="2" maxlength="600" placeholder="After run 2, the branch on line … (if … is false) is still not tested; the input … would test it."></textarea></div></section><!--/practice-build-->
<h3>A good build plan (2–3 sentences)</h3>
<div class="ex"><p class="tag">Strong</p><p>can_borrow refuses unless the item is available, the student holds fewer than LIMIT items and nothing is overdue. Any record it cannot read is refused. Tests: a normal loan, the third item (must be refused), an overdue student (must be refused) and a broken record (refused, not a crash).</p></div>
<details class="faq" id="solution"><summary><b>Show a complete solution with four tests</b> <span lang="ar" dir="rtl">اعرض حلاً كاملاً مع أربعة اختبارات</span></summary>
<pre id="exampleSolution">def can_borrow(student, item):
try:
loans = student["loans"]
if type(loans) is not int or loans < 0:
return False
return item["available"] is True and loans < LIMIT and student["overdue"] is False
except (KeyError, TypeError):
return False # fail-safe default: anything we cannot read is refused
def test_normal_loan_is_allowed():
assert can_borrow({"id": "s1", "loans": 0, "overdue": False}, {"id": "cam-3", "available": True})
def test_third_item_is_refused():
assert can_borrow({"id": "s1", "loans": 2, "overdue": False}, {"id": "cam-3", "available": True}) is False
def test_overdue_student_is_refused():
assert can_borrow({"id": "s1", "loans": 0, "overdue": True}, {"id": "cam-3", "available": True}) is False
def test_broken_record_is_refused_not_crashed():
assert can_borrow({}, {"id": "cam-3", "available": True}) is False</pre></details>
<h3>How to read the build record</h3>
<ul>
<li><b>✓ / ✗ under “Your tests”</b>: your own tests. A ✗ there means your code or your test is wrong. Fix one of them.</li>
<li><b>✓ / ✗ under “Course checks”</b>: the rule from the task. Each ✗ says what failed, for example “a student at the limit must be refused”.</li>
<li><b>“Known bugs … not caught”</b>: your tests pass, but they would also pass on a buggy version. Add a test that would fail on that bug. Example: delete <code>test_overdue_student_is_refused</code> above and run again; the record says “not caught: an overdue item is ignored”.</li>
<li><b>Micro-viva change request</b>: the change you may be asked to make in front of your instructor. Practise it: if you wrote the code yourself, it takes a minute.</li>
<li>If you cannot fix a failure before the deadline, keep the record and explain the failure honestly in your text. That still earns partial credit; hiding it does not.</li>
</ul></section>
<section class="section" id="stress"><h2>5 · New evidence (STRESS) and REFIT</h2>
<p>After you commit Part A, the <i>new evidence</i> opens on the page. It is a new fact that tests your boundary. For the practice case, imagine this:</p>
<div class="ex"><p class="tag">New evidence · practice</p><p>The library reports that the nightly copy failed on 3 of the last 30 nights. On those days the overdue flag was up to 48 hours old.</p></div>
<h3>Boundary status</h3>
<div class="ex"><p class="tag">Strong</p><p><b>CROSSED.</b> My boundary said the claim holds while the flag is at most 24 hours old. On 3 nights it was 48 hours old, so an overdue student could have borrowed.</p></div>
<h3>REFIT · RETAIN, REVISE or REPLACE</h3>
<div class="ex"><p class="tag">Strong</p><p><b>REVISE.</b> Keep the restricted opening, and add a fail-safe: when the copy is older than 24 hours, the kiosk refuses loans and sends students to the counter, where staff check the library record by hand.</p></div>
<p>A well-justified <b>RETAIN</b> earns the same credit as a change. Example: if the evidence had said the copy never failed, RETAIN with that reason is correct.</p>
<h3>Final engineering decision record (2–3 sentences)</h3>
<div class="ex"><p class="tag">Strong</p><p>Open the kiosk with restrictions (one item per student) once T2 and T3 pass on the real kiosk. When the overdue copy is older than 24 hours, the kiosk refuses and the counter checks by hand. Owner: lab manager; review after 30 days of copy timestamps.</p></div></section>
<section class="section" id="submit"><h2>6 · Declare AI use, sign and submit</h2>
<div class="ex"><p class="tag">AI-use declaration · two acceptable examples</p><p>“No AI used.”</p><p>“ChatGPT: I asked for edge cases for a loan rule. I kept two (third item, broken record) and rejected one (negative loans, already covered). I wrote and ran all code and tests myself.”</p></div>
<p>AI help is allowed when you declare it. You remain responsible: in the micro-viva you must explain and change your own code.</p>
<ol><li>Type your name for human review and tick the attestation.</li><li>Select <i>Print / PDF</i> (or <i>Download final editable .md</i> if asked), and open the file to check it.</li><li>Submit the final file in Blackboard before the deadline. Exporting on the site is not submission.</li></ol></section>
<!-- new-6-9:start --><section class="section" id="new-6-9"><h2>7 · Assignments 6–9: three new things <span class="vs-ar-t" lang="ar" dir="rtl">الواجبات 6–9: ثلاثة أشياء جديدة</span></h2><p>Same steps, same 5 points. The examples use the <a href="#example">practice case</a>, not your assignments. <span lang="ar" dir="rtl">نفس الخطوات ونفس الدرجة. الأمثلة على الحالة التدريبية وليست على واجباتك.</span></p><h3>A · What AI may do in each step <span lang="ar" dir="rtl" style="display:block;font-weight:600;font-size:.9em">أ · دور الذكاء الاصطناعي في كل خطوة</span></h3><div class="tablewrap"><table><thead><tr><th scope="col">Step · الخطوة</th><th scope="col">What AI may do · دور الذكاء الاصطناعي</th></tr></thead><tbody><tr><th scope="row">Studying the lecture before you start<br/><span lang="ar" dir="rtl">قبل البدء: مذاكرة المحاضرة</span></th><td>AI may explain an idea and ask you to restate it in your own words. It may not answer this case.<br/><span lang="ar" dir="rtl">يمكن للذكاء الاصطناعي أن يشرح فكرة ويطلب منك إعادة صياغتها بكلامك، ولا يجيب عن حالة هذا الواجب.</span></td></tr><tr><th scope="row">Part A answers (FIT, COMPARE, BOUND, ACT + EVIDENCE, reading, second idea)<br/><span lang="ar" dir="rtl">إجابات الجزء A</span></th><td>No AI. Write them yourself.<br/><span lang="ar" dir="rtl">بدون ذكاء اصطناعي. اكتبها بنفسك.</span></td></tr><tr><th scope="row">The AI draft in the scenario<br/><span lang="ar" dir="rtl">مسودة الذكاء الاصطناعي في الحالة</span></th><td>It is the material you critique. Find the claims the supplied facts do not support.<br/><span lang="ar" dir="rtl">هي المادة التي تنقدها: حدّد الادعاءات التي لا تدعمها الحقائق المعطاة.</span></td></tr><tr><th scope="row">Python build<br/><span lang="ar" dir="rtl">البناء في بايثون</span></th><td>AI may explain an error message. It may not write your functions or your tests.<br/><span lang="ar" dir="rtl">يمكن للذكاء الاصطناعي أن يشرح رسالة الخطأ، ولا يكتب دوالك ولا اختباراتك.</span></td></tr><tr><th scope="row">After you commit (STRESS and REFIT)<br/><span lang="ar" dir="rtl">بعد تثبيت الجزء A</span></th><td>AI may question your reasoning. The final decision and the words are yours.<br/><span lang="ar" dir="rtl">يمكن للذكاء الاصطناعي أن يسألك عن تفكيرك، والقرار الأخير وصياغته لك.</span></td></tr><tr><th scope="row">AI-use declaration<br/><span lang="ar" dir="rtl">تصريح استخدام الذكاء الاصطناعي</span></th><td>Name the tool and the steps where you used it, what you changed because of it, and what you chose not to take, and why.<br/><span lang="ar" dir="rtl">اذكر الأداة والخطوات التي استخدمتها فيها، وما غيّرته بسببها، وما رفضت أخذه ولماذا.</span></td></tr></tbody></table></div><p>✓ “My build says <code>KeyError: 'loans'</code>. What does this error mean?” Then you fix it. ✗ “Write reserve_minutes and three tests for it.”</p><h3>B · Critique the AI draft <span lang="ar" dir="rtl" style="display:block;font-weight:600;font-size:.9em">ب · انقد مسودة الذكاء الاصطناعي</span></h3><p>For each claim in the draft: find the supplied fact that supports it, contradicts it, or says nothing, and write your verdict. This counts inside FIT; then write your own answer.</p><p lang="ar" dir="rtl">لكل ادعاء في المسودة: ابحث عن الحقيقة المعطاة التي تدعمه أو تنقضه، واكتب حكمك. هذا جزء من FIT، ثم اكتب إجابتك أنت.</p><div class="ex"><p class="tag">Practice AI draft · not assessed</p><p>“Open the kiosk now. It passed every test, so the loan rule works. The overdue flag comes from the library system, so it is always correct. A student can never take a third item, because the rule says at most two.”</p></div><details class="faq"><summary><b>Show a strong critique</b> <span lang="ar" dir="rtl">اعرض نقداً قوياً</span></summary><ol><li><b>“It passed every test, so the loan rule works.”</b> Not supported. The tests only borrowed one item; no test tried a third item or an overdue student.</li><li><b>“The overdue flag is always correct.”</b> Not supported. The flag is copied once a night at 02:00, so it can be almost a day old.</li><li><b>“A student can never take a third item.”</b> Not supported. Writing the rule is not the same as enforcing it, and nothing has tested the third item.</li></ol><p>✗ Weak: “The AI draft is too confident and wrong.” It names no claim and no fact.</p></details><h3>C · The in-class check <span lang="ar" dir="rtl" style="display:block;font-weight:600;font-size:.9em">ج · التحقق في المحاضرة</span></h3><p><b>In-class check.</b> In the lecture after the deadline you answer two short questions on paper, with no devices, about your own submission: a small change to your code (which test fails, and why) and one new fact (does your decision change?). It takes about 10 minutes. Both answers correct: your assignment mark stands. One correct: your mark is halved. Neither: a two-minute conversation with the instructor decides the mark. If you miss that lecture with an accepted excuse, the check is done in office hours. This applies the ownership rule on every assignment: your written work and your own explanation must agree.</p><p lang="ar" dir="rtl"><b>التحقق في المحاضرة.</b> في المحاضرة التي تلي موعد التسليم تجيب على ورق، وبدون أي جهاز، عن سؤالين قصيرين حول واجبك أنت: تغيير صغير في الكود (أي اختبار سيفشل ولماذا)، ومعلومة جديدة (هل يتغير قرارك؟). يستغرق نحو 10 دقائق. إذا كانت الإجابتان صحيحتين تبقى درجة الواجب كما هي، وإذا كانت إجابة واحدة صحيحة تصبح نصف الدرجة، وإذا لم تصح أي منهما تُحدَّد الدرجة بعد نقاش قصير مع الأستاذ مدته دقيقتان. ومن غاب عن تلك المحاضرة بعذر مقبول يُجري التحقق في الساعات المكتبية. هذا تطبيق لقاعدة الملكية الموجودة في كل واجب: يجب أن يتفق ما كتبته مع شرحك له.</p><p>Two sample questions on the <a href="#build">practice build</a>. Answer first, then open:</p><details class="faq"><summary><b>1 · Change <code>loans < LIMIT</code> to <code>loans <= LIMIT</code>. Which test fails, and why?</b> <span lang="ar" dir="rtl">السؤال 1 · تغيير صغير في الكود</span></summary><p>✓ test_third_item_is_refused fails: a student who already holds 2 items is now lent a third. The other three tests still pass, because none of them holds 2 items.</p><p>✗ “All the tests fail because the code is wrong.”</p></details><details class="faq"><summary><b>2 · The library now copies the overdue flag every hour. Does your decision change?</b> <span lang="ar" dir="rtl">السؤال 2 · معلومة جديدة</span></summary><p>✓ RETAIN. My boundary (flag at most 24 hours old) now holds more strongly, but T2 and T3 have still not run on the real kiosk, so I keep the restricted opening until they pass.</p><p>✗ “Yes, now it is safe, so open the kiosk.”</p></details><p><b>Prepare:</b> read your submission once more, run your tests and know which test catches which bug, and say your BOUND in one sentence. <b>Declare:</b> name the tool, the steps, what you changed and what you rejected, or write “No AI used.”</p><p lang="ar" dir="rtl"><b>استعد:</b> اقرأ ما سلّمته، وشغّل اختباراتك واعرف أي اختبار يكشف أي خطأ، وقل حدود قرارك في جملة. <b>صرّح:</b> اذكر الأداة والخطوات وما غيّرته وما رفضته، أو اكتب “No AI used.”</p></section><!-- new-6-9:end -->
<section class="section" id="faq"><h2>8 · Questions students ask <span class="vs-ar-t" lang="ar" dir="rtl">أسئلة الطلاب</span></h2><style>details.faq{border-bottom:1px solid color-mix(in srgb,currentColor 15%,transparent);padding:8px 0}details.faq summary{cursor:pointer}details.faq summary span{display:block;font-weight:600;margin-top:2px}</style><details class="faq"><summary><b>Is “Review & commit Part A” my submission?</b> <span lang="ar" dir="rtl">هل Review & commit هو التسليم؟ لا.</span></summary><p>No. It freezes Part A. You submit the final PDF in Blackboard at the end.</p></details><details class="faq"><summary><b>Can I change Part A after committing?</b> <span lang="ar" dir="rtl">هل أعدّل الجزء A بعد التثبيت؟ لا، عدّل في الجزء B.</span></summary><p>No. Use Part B (REFIT) to revise your decision. That is the point of the method.</p></details><details class="faq"><summary><b>I opened the assignment on another computer and my draft is gone.</b> <span lang="ar" dir="rtl">فتحت من جهاز آخر واختفت مسودتي.</span></summary><p>Drafts stay in the browser where you wrote them. Go back to that browser, or use <i>Download draft / backup</i> before you switch.</p></details><details class="faq"><summary><b>Python does not load.</b> <span lang="ar" dir="rtl">بايثون لا يعمل.</span></summary><p>The first run downloads Python once (about 10 MB), so you need internet. Use a current Chrome, Edge, Firefox or Safari on a laptop, and try again.</p></details><details class="faq"><summary><b>A course check fails and I cannot fix it.</b> <span lang="ar" dir="rtl">فحص لا ينجح ولا أعرف إصلاحه.</span></summary><p>Keep the record and explain the failure in your text. An honest explanation earns partial credit.</p></details><details class="faq"><summary><b>It says a known bug is “not caught”.</b> <span lang="ar" dir="rtl">يظهر لي «not caught».</span></summary><p>Your tests are too easy. Add a test that a buggy version would fail. The bug's name tells you what to test.</p></details><details class="faq"><summary><b>What do I upload to Blackboard, and when?</b> <span lang="ar" dir="rtl">ماذا أرفع في Blackboard ومتى؟ ملف PDF النهائي فقط، مرة واحدة.</span></summary><p>Only the final PDF, once, to the assignment’s item in Blackboard, before the deadline. You do not upload Part A separately.</p></details><details class="faq"><summary><b>How many words?</b> <span lang="ar" dir="rtl">كم كلمة؟ 120–180، والكود لا يُحسب.</span></summary><p>About 120–180 for the written answers. Code, tests and the build record are not counted.</p></details><details class="faq"><summary><b>Can I use ChatGPT or another AI?</b> <span lang="ar" dir="rtl">هل أستخدم ChatGPT؟ نعم، مع التصريح.</span></summary><p>Yes, if you declare it and check its output. If you cannot explain or change your own code in the micro-viva, the build loses its credit. In Assignments 6–9, Part A is written without AI: see <a href="#new-6-9">what AI may do in each step</a>.</p></details><details class="faq"><summary><b>What is the micro-viva?</b> <span lang="ar" dir="rtl">ما المقابلة القصيرة؟ دقيقتان تشرح فيهما عملك.</span></summary><p>A two-minute conversation with a few students per section. You explain your work and make the change request named in your build record.</p></details><details class="faq"><summary><b>Where do I ask for help?</b> <span lang="ar" dir="rtl">أين أسأل؟ في الكلاس أو الساعات المكتبية أو البريد.</span></summary><p>In class, in office hours, or by email to the instructor, with your Student ID and the assignment number.</p></details></section>
</main><script>/* Python build tasks for Assignments 3–9 (generated by tools/apply_assignment_build.py).
The student's code runs locally with Pyodide in a Web Worker, with a time limit. Nothing is uploaded. */
var StudyBuild=(function(){
'use strict';
var BASE=(typeof window!=='undefined'&&window.ISCARB_PYODIDE_BASE)||"https://cdn.jsdelivr.net/pyodide/v0.26.4/full/";
/* The same Pyodide files are copied on this site (vendor/pyodide). They are used when the CDN download fails, for example on a blocked or unstable network. */
var MIRROR=(function(){try{if(!/^https?:$/.test(location.protocol))return '';var p=location.pathname,i=p.indexOf('/lectures/iscarb/');return location.origin+(i>=0?p.slice(0,i+1):p.slice(0,p.lastIndexOf('/')+1))+'vendor/pyodide/0.26.4/';}catch(e){return '';}})(),src=BASE,std='';
var HARNESS="import ast, json, os, sys, traceback\n\n# os._exit() would stop Python itself and look like a broken runner. In the student's code it raises SystemExit.\ndef __exit_now(code=0):\n raise SystemExit(code)\n\nos._exit = __exit_now\n\ndef __coverage_map(student):\n \"\"\"Lines and branches of the code under test: every function in your_code.py whose name does not start with test_.\"\"\"\n tree = ast.parse(student)\n lines, branches = set(), {}\n src = student.split(\"\\n\")\n for f in ast.walk(tree):\n if not isinstance(f, (ast.FunctionDef, ast.AsyncFunctionDef)) or f.name.startswith(\"test_\"):\n continue\n for n in ast.walk(f):\n if n is f or isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):\n continue\n if isinstance(n, ast.stmt) and not (isinstance(n, ast.Expr) and isinstance(getattr(n, \"value\", None), ast.Constant) and isinstance(n.value.value, str)):\n lines.add(n.lineno)\n if isinstance(n, (ast.If, ast.While, ast.For)) and n.body and n.body[0].lineno != n.lineno:\n text = src[n.lineno - 1].strip()\n if isinstance(n, ast.If):\n other = n.orelse[0].lineno if n.orelse else None\n branches[n.lineno] = [text, n.body[0].lineno, other, \"true\", \"false\"]\n else:\n branches[n.lineno] = [text, n.body[0].lineno, None, \"loop body runs\", \"loop is skipped or ends\"]\n return lines, branches\n\ndef __tracer(hit, arcs):\n def call(frame, event, arg):\n code = frame.f_code\n if code.co_filename != \"your_code.py\" or code.co_name.startswith(\"test_\") or code.co_name == \"<module>\":\n return None\n last = [None]\n def local(frame, event, arg):\n if event == \"line\":\n hit.add(frame.f_lineno); arcs.add((last[0], frame.f_lineno)); last[0] = frame.f_lineno\n elif event == \"return\":\n arcs.add((last[0], -1))\n return local\n return local\n return call\n\ndef __coverage(student, hit, arcs):\n try:\n lines, branches = __coverage_map(student)\n except BaseException:\n return None\n rows = []\n for line in sorted(branches):\n text, first, other, yes, no = branches[line]\n took_yes = (line, first) in arcs\n took_no = (line, other) in arcs if other else any(a == line and b != first for a, b in arcs)\n rows.append([line, text[:70], yes, took_yes, no, took_no])\n return {\"lines\": [len(lines & hit), len(lines)], \"missed\": sorted(lines - hit),\n \"branches\": [sum(r[3] + r[5] for r in rows), 2 * len(rows)], \"rows\": rows}\n\ndef __last_line(e):\n return type(e).__name__ + (\": \" + str(e) if str(e) else \"\")\n\ndef __mutate(helpers, student, mutants, tests):\n \"\"\"Run your tests against known buggy versions. A bug is caught when at least one test fails.\"\"\"\n spec = {}\n exec(mutants, spec)\n res = []\n for label, code in spec[\"MUTANTS\"]:\n mns = {\"__name__\": \"student\"}\n try:\n exec(helpers, mns)\n exec(compile(student, \"your_code.py\", \"exec\"), mns)\n exec(code, mns)\n except BaseException:\n res.append([label, True]); continue\n killed = False\n for name in tests:\n try:\n mns[name]()\n except BaseException:\n killed = True\n break\n res.append([label, killed])\n return res\n\ndef __run(helpers, student, checks, mutants=\"\"):\n ns = {\"__name__\": \"student\"}\n out = {\"tests\": [], \"checks\": [], \"error\": None}\n try:\n exec(helpers, ns)\n except BaseException as e:\n out[\"error\"] = \"Course helpers failed: \" + __last_line(e)\n return json.dumps(out)\n base = dict(ns) # the checks use the very same helper classes as the student code\n try:\n exec(compile(student, \"your_code.py\", \"exec\"), ns)\n except SyntaxError as e:\n out[\"error\"] = f\"Your code did not run (line {e.lineno}): SyntaxError: {e.msg}\"\n return json.dumps(out)\n except BaseException as e:\n tb = traceback.extract_tb(e.__traceback__)\n line = next((f.lineno for f in reversed(tb) if f.filename == \"your_code.py\"), None)\n out[\"error\"] = \"Your code did not run\" + (f\" (line {line})\" if line else \"\") + \": \" + __last_line(e)\n return json.dumps(out)\n ns[\"__source__\"] = student\n hit, arcs = set(), set()\n for name in sorted(k for k, v in list(ns.items()) if k.startswith(\"test_\") and callable(v)):\n sys.settrace(__tracer(hit, arcs)) # coverage counts only what your own tests run\n try:\n ns[name]()\n out[\"tests\"].append([name, True, \"\"])\n except AssertionError as e:\n out[\"tests\"].append([name, False, \"assertion failed\" + (\": \" + str(e) if str(e) else \"\")])\n except BaseException as e:\n out[\"tests\"].append([name, False, __last_line(e)])\n finally:\n sys.settrace(None)\n cov = __coverage(student, hit, arcs)\n if (not cov or not cov[\"lines\"][1]) and callable(ns.get(\"_tree_coverage\")):\n try:\n cov = ns[\"_tree_coverage\"](ns) # a build without functions (the fault tree) measures its gates instead\n except BaseException:\n pass\n out[\"coverage\"] = cov\n cns = dict(base)\n cns[\"__name__\"] = \"checks\"\n exec(checks, cns)\n def own_tests(ns, out):\n t = out[\"tests\"]\n assert len(t) >= 3, f\"write at least three test_ functions (found {len(t)})\"\n bad = [n for n, ok, _ in t if not ok]\n assert not bad, \"failing: \" + \", \".join(bad)\n extra = [(\"your own tests: at least three, all passing\", own_tests)]\n if mutants:\n out[\"mutants\"] = __mutate(helpers, student, mutants, [t[0] for t in out[\"tests\"]])\n def catches(ns, out):\n assert out[\"tests\"] and all(ok for _, ok, _ in out[\"tests\"]), \"first make your own tests pass on your code\"\n missed = [m for m, killed in out[\"mutants\"] if not killed]\n assert not missed, f\"your tests catch {len(out['mutants']) - len(missed)} of {len(out['mutants'])} known bugs; not caught: \" + \"; \".join(missed)\n extra.append((\"your tests catch every known bug (mutation test)\", catches))\n for label, fn in cns[\"CHECKS\"] + extra:\n try:\n fn(ns, out)\n out[\"checks\"].append([label, True, \"\"])\n except AssertionError as e:\n out[\"checks\"].append([label, False, str(e) or \"not met\"])\n except BaseException as e:\n out[\"checks\"].append([label, False, \"your code raised \" + __last_line(e)])\n return json.dumps(out)\n\ndef __their(helpers, student, their):\n \"\"\"Run 3 · their change: a part you did not write changes; your code and tests stay as they are.\"\"\"\n ns = {\"__name__\": \"student\"}\n out = {\"tests\": [], \"evidence\": [], \"correct\": None, \"error\": None}\n tns = {\"__name__\": \"their\"}\n try:\n exec(helpers, ns)\n exec(their, tns)\n tns[\"install\"](ns)\n exec(compile(student, \"your_code.py\", \"exec\"), ns)\n except BaseException as e:\n out[\"error\"] = \"Your code did not run: \" + __last_line(e)\n return json.dumps(out)\n for name in sorted(k for k, v in list(ns.items()) if k.startswith(\"test_\") and callable(v)):\n try:\n ns[name]()\n out[\"tests\"].append([name, True, \"\"])\n except AssertionError as e:\n out[\"tests\"].append([name, False, \"assertion failed\" + (\": \" + str(e) if str(e) else \"\")])\n except BaseException as e:\n out[\"tests\"].append([name, False, __last_line(e)])\n try:\n lines, correct = tns[\"scenario\"](ns)\n out[\"evidence\"], out[\"correct\"] = [str(x) for x in lines], bool(correct)\n except BaseException as e:\n out[\"evidence\"], out[\"correct\"] = [\"your code raised \" + __last_line(e) + \" on the shared case\"], False\n return json.dumps(out)\n";
var SPEC={"0": {"helpers": "# Practice task (not assessed). No helpers: you write the rule yourself.\nLIMIT = 2 # the lab lends at most two items to one student at a time\n", "starter": "# Practice build: the loan rule for the lab-equipment desk (not assessed).\n# student = {\"id\": \"s1\", \"loans\": 1, \"overdue\": False} loans = items the student holds now\n# item = {\"id\": \"cam-3\", \"available\": True}\n# Rule: lend only if the item is available, the student holds fewer than LIMIT items and has\n# nothing overdue. Anything else, including malformed input, is refused (False).\n\ndef can_borrow(student, item):\n return item[\"available\"] # CHANGE ME: today the desk only checks the shelf\n\n# Your tests: at least three functions whose names start with test_\n# The course also runs your tests against known buggy versions: each bug must make one of them fail.\ndef test_normal_loan_is_allowed():\n assert can_borrow({\"id\": \"s1\", \"loans\": 0, \"overdue\": False}, {\"id\": \"cam-3\", \"available\": True})\n", "checks": "def _f(ns):\n f = ns.get(\"can_borrow\"); assert callable(f), \"define can_borrow(student, item)\"\n return f\nOK = {\"id\": \"cam-3\", \"available\": True}\n\ndef allowed(ns, out):\n assert _f(ns)({\"id\": \"s1\", \"loans\": 1, \"overdue\": False}, OK) is True, \"a student with one item and nothing overdue may borrow (return True)\"\ndef limit(ns, out):\n assert _f(ns)({\"id\": \"s1\", \"loans\": LIMIT, \"overdue\": False}, OK) is False, \"a student who already holds LIMIT items must be refused\"\ndef overdue(ns, out):\n assert _f(ns)({\"id\": \"s1\", \"loans\": 0, \"overdue\": True}, OK) is False, \"a student with an overdue item must be refused\"\ndef unavailable(ns, out):\n assert _f(ns)({\"id\": \"s1\", \"loans\": 0, \"overdue\": False}, {\"id\": \"cam-3\", \"available\": False}) is False, \"an item that is out must be refused\"\ndef malformed(ns, out):\n f = _f(ns)\n for s, i in [({}, OK), (None, OK), ({\"id\": \"s1\", \"loans\": 0, \"overdue\": False}, None), ({\"id\": \"s1\", \"loans\": \"0\", \"overdue\": False}, OK)]:\n try:\n r = f(s, i)\n except Exception as e:\n raise AssertionError(f\"malformed input must be refused, not crash ({type(e).__name__})\")\n assert r is False, f\"malformed input must be refused: {s!r}, {i!r}\"\n\nCHECKS = [\n (\"a normal loan is allowed\", allowed),\n (\"a student at the limit is refused\", limit),\n (\"a student with an overdue item is refused\", overdue),\n (\"an item that is out is refused\", unavailable),\n (\"malformed input is refused without crashing\", malformed),\n]\n", "mutants": "# Known bugs. Your own tests must catch each one (at least one of your tests fails).\nMUTANTS = [\n (\"off by one: a third item is lent\", '''\ndef can_borrow(student, item):\n try:\n return bool(item[\"available\"]) and type(student[\"loans\"]) is int and student[\"loans\"] <= LIMIT and student[\"overdue\"] is False\n except Exception:\n return False\n'''),\n (\"an overdue item is ignored\", '''\ndef can_borrow(student, item):\n try:\n return bool(item[\"available\"]) and type(student[\"loans\"]) is int and student[\"loans\"] < LIMIT\n except Exception:\n return False\n'''),\n (\"malformed input crashes instead of being refused\", '''\ndef can_borrow(student, item):\n return bool(item[\"available\"]) and student[\"loans\"] < LIMIT and student[\"overdue\"] is False\n'''),\n]\n", "title": "Practice build: the loan rule", "criterion": "written answers", "assignment": null, "checkLabels": ["a normal loan is allowed", "a student at the limit is refused", "a student with an overdue item is refused", "an item that is out is refused", "malformed input is refused without crashing", "your own tests: at least three, all passing", "your tests catch every known bug (mutation test)"], "viva": ["The lab now lends three items. Change one thing and say which test changes.", "Delete your overdue test and run again. Which known bug is no longer caught?", "A student record arrives with loans as the text \"1\". What does your code do, and why is that safe?", "Students with a lab permit may borrow one extra item. Add the rule and one refusal test."]}, "12": {"helpers": "class Event:\n def __init__(self, name, source=\"fact\"):\n if source not in (\"fact\", \"assumption\", \"proposed\"):\n raise ValueError(\"source must be 'fact', 'assumption' or 'proposed'\")\n self.name, self.source = str(name), source\n def __repr__(self):\n return f\"Event({self.name!r}, {self.source!r})\"\n\nclass Gate:\n def __init__(self, kind, children):\n children = list(children)\n if len(children) < 2:\n raise ValueError(f\"{kind} needs at least two inputs\")\n for c in children:\n if not isinstance(c, (Event, Gate)):\n raise TypeError(f\"{kind} inputs must be Event(...), AND(...) or OR(...)\")\n self.kind, self.children = kind, children\n\ndef AND(*children):\n return Gate(\"AND\", children)\n\ndef OR(*children):\n return Gate(\"OR\", children)\n\ndef events(tree):\n if isinstance(tree, Event):\n return [tree]\n seen, out = set(), []\n for c in tree.children:\n for e in events(c):\n if e.name not in seen:\n seen.add(e.name); out.append(e)\n return out\n\ndef _sets(tree):\n if isinstance(tree, Event):\n return [frozenset([tree.name])]\n parts = [_sets(c) for c in tree.children]\n if tree.kind == \"OR\":\n return [s for p in parts for s in p]\n acc = [frozenset()]\n for p in parts:\n acc = [a | s for a in acc for s in p]\n return acc\n\ndef cut_sets(tree):\n \"\"\"Minimal cut sets: the smallest combinations of events that cause the top event.\"\"\"\n sets = set(_sets(tree))\n minimal = [s for s in sets if not any(o < s for o in sets)]\n return sorted((tuple(sorted(s)) for s in minimal), key=lambda s: (len(s), s))\n\ndef single_points(tree):\n \"\"\"Events that cause the top event on their own.\"\"\"\n return [s[0] for s in cut_sets(tree) if len(s) == 1]\n\n_GATE_SEEN = {}\n\ndef occurs(tree, happened):\n \"\"\"True when the top event happens, given the names of the basic events that happen.\"\"\"\n happened = set(happened)\n def value(t):\n if isinstance(t, Event):\n return t.name in happened\n r = any(value(c) for c in t.children) if t.kind == \"OR\" else all(value(c) for c in t.children)\n _GATE_SEEN.setdefault(id(t), set()).add(r)\n return r\n return value(tree)\n\ndef _tree_coverage(ns):\n \"\"\"Coverage for a fault tree: did your tests see each gate both open (true) and blocked (false)?\"\"\"\n rows = []\n def walk(name, g):\n if isinstance(g, Event):\n return\n seen = _GATE_SEEN.get(id(g), set())\n inputs = \", \".join(c.name[:18] if isinstance(c, Event) else c.kind + \"(…)\" for c in g.children)\n rows.append([name + \" · \" + g.kind + \" gate\", (g.kind + \"(\" + inputs + \")\")[:70], \"true (the path to the hazard is open)\", True in seen, \"false (the path is blocked)\", False in seen])\n for c in g.children:\n walk(name, c)\n for name in (\"current\", \"revised\"):\n if isinstance(ns.get(name), Gate):\n walk(name, ns[name])\n return {\"unit\": \"gates\", \"lines\": [sum(1 for r in rows if r[3] or r[5]), len(rows)], \"missed\": [],\n \"branches\": [sum(r[3] + r[5] for r in rows), 2 * len(rows)], \"rows\": rows}\n", "starter": "# Assignment 3 build: a fault tree for \"the barrier closes while the zone is occupied\".\n# Tools: Event(name, source) AND(a, b, ...) OR(a, b, ...) cut_sets(tree) single_points(tree)\n# occurs(tree, {\"event name\", ...}) is True when the hazard happens given the events that happen.\n# source is \"fact\" (stated in the case), \"assumption\" or \"proposed\" (your new control or test).\n\ncurrent = OR(\n Event(\"CHANGE ME: a cause stated in the case\", \"fact\"),\n Event(\"CHANGE ME: another cause\", \"assumption\"),\n)\n\nrevised = AND(\n Event(\"CHANGE ME: a cause\", \"fact\"),\n Event(\"CHANGE ME: your proposed control\", \"proposed\"),\n)\n\n# Your tests: at least three functions whose names start with test_\ndef test_example():\n assert cut_sets(current), \"every tree has at least one cut set\"\n\ndef test_nothing_happens():\n assert not occurs(revised, set()), \"with no event, the hazard does not happen\"\n", "checks": "def _tree(ns, name):\n t = ns.get(name)\n assert t is not None, f\"define {name}\"\n assert isinstance(t, Gate), f\"{name} must start with AND(...) or OR(...)\"\n return t\n\ndef complete(ns, out):\n for n in (\"current\", \"revised\"):\n ev = events(_tree(ns, n))\n assert len(ev) >= 3, f\"{n} needs at least three events (has {len(ev)})\"\n left = [e.name for e in ev if \"CHANGE ME\" in e.name]\n assert not left, f\"{n} still has placeholders: \" + \"; \".join(left)\n\ndef rests_on_facts(ns, out):\n assert any(e.source == \"fact\" for e in events(_tree(ns, \"current\"))), \"current must use at least one case fact\"\n\ndef no_single_point(ns, out):\n sp = single_points(_tree(ns, \"revised\"))\n assert not sp, \"in revised, these events still cause the hazard alone: \" + \"; \".join(sp)\n\ndef proposed_marked(ns, out):\n assert any(e.source == \"proposed\" for e in events(_tree(ns, \"revised\"))), \"label your new control or test as proposed\"\n\ndef keeps_facts(ns, out):\n facts = {e.name for e in events(_tree(ns, \"current\")) if e.source == \"fact\"}\n kept = {e.name for e in events(_tree(ns, \"revised\"))}\n missing = sorted(facts - kept)\n assert not missing, \"revised drops case facts (add protection; do not delete causes): \" + \"; \".join(missing)\n\nCHECKS = [\n (\"both trees are complete: three or more events, no placeholders\", complete),\n (\"current rests on case facts\", rests_on_facts),\n (\"revised: no single event causes the hazard\", no_single_point),\n (\"revised labels new controls or tests as proposed\", proposed_marked),\n (\"revised keeps every case fact from current\", keeps_facts),\n]\n", "title": "Build the fault tree", "criterion": "TRACE", "assignment": 3, "checkLabels": ["both trees are complete: three or more events, no placeholders", "current rests on case facts", "revised: no single event causes the hazard", "revised labels new controls or tests as proposed", "revised keeps every case fact from current", "your own tests: at least three, all passing"], "viva": ["The night tests now exist and the camera passed them. Change your trees: which event changes its source tag, and does any cut set change?", "Remove your proposed control from revised and run again. Which check fails, and what does it say about the barrier?", "An auditor says the stop button is enough. Show with cut_sets why it is or is not.", "Add rain as a new fact under the camera. Where does it belong in current, and does revised still pass?"]}, "13": {"helpers": "# No helpers: you write the server-side rule yourself.\n", "starter": "# Assignment 4 build: the server-side authorization rule for the project portal.\n# user = {\"id\": \"s1\", \"role\": \"student\" or \"staff\", \"groups\": [\"G1\", ...]}\n# project = {\"id\": \"p7\", \"owner\": \"s1\", \"group\": \"G1\"}\n# Rule: a student may read only a project they own. Staff may read only projects in a\n# group assigned to them. Anything else, including malformed input, is refused (False).\n\ndef can_read(user, project):\n return True # CHANGE ME: today the API serves any project identifier it receives\n\n# Your tests: at least three functions whose names start with test_\n# The course also runs your tests against known buggy versions: each bug must make one of them fail.\n# Include at least two requests that must be refused.\ndef test_owner_can_read():\n assert can_read({\"id\": \"s1\", \"role\": \"student\", \"groups\": []},\n {\"id\": \"p1\", \"owner\": \"s1\", \"group\": \"G1\"})\n", "checks": "def _f(ns):\n f = ns.get(\"can_read\"); assert callable(f), \"define can_read(user, project)\"\n return f\nS1 = {\"id\": \"s1\", \"role\": \"student\", \"groups\": [\"G1\"]}\nS2 = {\"id\": \"s2\", \"role\": \"student\", \"groups\": [\"G1\"]}\nT1 = {\"id\": \"t1\", \"role\": \"staff\", \"groups\": [\"G1\"]}\nT2 = {\"id\": \"t2\", \"role\": \"staff\", \"groups\": [\"G2\"]}\nP = {\"id\": \"p1\", \"owner\": \"s1\", \"group\": \"G1\"}\n\ndef owner(ns, out):\n assert _f(ns)(S1, P) is True, \"the owner must be allowed (return True)\"\ndef other_student(ns, out):\n assert _f(ns)(S2, P) is False, \"another student in the same group must be refused\"\ndef assigned_staff(ns, out):\n assert _f(ns)(T1, P) is True, \"staff assigned to the project's group must be allowed\"\ndef other_staff(ns, out):\n assert _f(ns)(T2, P) is False, \"staff from another group must be refused\"\ndef unknown_role(ns, out):\n assert _f(ns)({\"id\": \"s1\", \"role\": \"admin\", \"groups\": [\"G1\"]}, P) is False, \"an unknown role must be refused\"\ndef malformed(ns, out):\n f = _f(ns)\n for u, p in [({}, P), (S1, {}), (None, P), (S1, None), ({\"id\": \"t1\", \"role\": \"staff\"}, P)]:\n try:\n r = f(u, p)\n except Exception as e:\n raise AssertionError(f\"malformed input must be refused, not crash ({type(e).__name__})\")\n assert r is False, f\"malformed input must be refused: {u!r}, {p!r}\"\ndef tests_refuse(ns, out):\n src = ns.get(\"__source__\", \"\")\n n = src.count(\"not can_read(\") + src.count(\"is False\") + src.count(\"== False\")\n assert n >= 2, \"your tests must check at least two requests that are refused\"\n\nCHECKS = [\n (\"the owner can read\", owner),\n (\"another student is refused, even in the same group\", other_student),\n (\"assigned staff can read\", assigned_staff),\n (\"staff from another group is refused\", other_staff),\n (\"an unknown role is refused\", unknown_role),\n (\"malformed input is refused without crashing\", malformed),\n (\"your tests include at least two refusals\", tests_refuse),\n]\n", "mutants": "# Known bugs. Your own tests must catch each one (at least one of your tests fails).\nMUTANTS = [\n (\"the group is enough for a student (the hidden-link bug)\", '''\ndef can_read(user, project):\n try:\n if user[\"role\"] in (\"student\", \"staff\"):\n return project[\"group\"] in user[\"groups\"]\n except Exception:\n return False\n return False\n'''),\n (\"any staff member can read any project\", '''\ndef can_read(user, project):\n try:\n if user[\"role\"] == \"staff\":\n return True\n if user[\"role\"] == \"student\":\n return project[\"owner\"] == user[\"id\"]\n except Exception:\n return False\n return False\n'''),\n (\"an unknown role is treated like staff\", '''\ndef can_read(user, project):\n try:\n if user[\"role\"] == \"student\":\n return project[\"owner\"] == user[\"id\"]\n return project[\"group\"] in user[\"groups\"]\n except Exception:\n return False\n'''),\n (\"malformed input crashes instead of being refused\", '''\ndef can_read(user, project):\n if user[\"role\"] == \"student\":\n return project[\"owner\"] == user[\"id\"]\n if user[\"role\"] == \"staff\":\n return project[\"group\"] in user[\"groups\"]\n return False\n'''),\n]\n", "title": "Build the server-side check", "criterion": "TEST", "assignment": 4, "checkLabels": ["the owner can read", "another student is refused, even in the same group", "assigned staff can read", "staff from another group is refused", "an unknown role is refused", "malformed input is refused without crashing", "your tests include at least two refusals", "your own tests: at least three, all passing", "your tests catch every known bug (mutation test)"], "viva": ["A new role “ta” is added and must read only its assigned groups. Change can_read and add one test that must be refused.", "A project may now have two owners (owner is a list). Change can_read; which of your tests changes?", "Remove your malformed-input handling and run again. Which known bug is no longer caught, and why?", "Staff may now read a project only while it is not archived. Add the rule and one refusal test."]}, "14": {"helpers": "import copy\n", "starter": "# Assignment 5 build: reconcile the controlled paper log after the central server recovers.\n# central = {\"P12\": {\"time\": \"08:15\", \"version\": 3}, ...}\n# paper_log = [{\"change_id\": \"c1\", \"pickup_id\": \"P12\", \"time\": \"08:40\", \"base_version\": 3}, ...]\n# Rules: apply each change once (same change_id twice = once). If the central version moved\n# since the change was written (version != base_version), do not overwrite: record a conflict.\n# A pickup missing centrally is added (base_version 0). Each applied change adds 1 to version.\n# Return (new_central, conflicts): conflicts is a list of change_ids. Do not modify the inputs.\n\ndef reconcile(central, paper_log):\n new = dict(central)\n for c in paper_log:\n new[c[\"pickup_id\"]] = {\"time\": c[\"time\"], \"version\": c[\"base_version\"] + 1}\n return new, [] # CHANGE ME\n\n# Your tests: at least three functions whose names start with test_\n# The course also runs your tests against known buggy versions: each bug must make one of them fail.\ndef test_one_change_is_applied():\n new, conflicts = reconcile({\"P1\": {\"time\": \"08:00\", \"version\": 1}},\n [{\"change_id\": \"c1\", \"pickup_id\": \"P1\", \"time\": \"08:30\", \"base_version\": 1}])\n assert new[\"P1\"] == {\"time\": \"08:30\", \"version\": 2} and conflicts == []\n", "checks": "def _r(ns):\n f = ns.get(\"reconcile\"); assert callable(f), \"define reconcile(central, paper_log)\"\n return f\ndef _c(cid, pid, t, base):\n return {\"change_id\": cid, \"pickup_id\": pid, \"time\": t, \"base_version\": base}\n\ndef applies(ns, out):\n new, conf = _r(ns)({\"P1\": {\"time\": \"08:00\", \"version\": 1}}, [_c(\"c1\", \"P1\", \"08:30\", 1)])\n assert new[\"P1\"] == {\"time\": \"08:30\", \"version\": 2}, f\"one change should give version 2 at 08:30, got {new.get('P1')}\"\n assert list(conf) == [], \"no conflict expected\"\ndef once(ns, out):\n new, conf = _r(ns)({\"P1\": {\"time\": \"08:00\", \"version\": 1}}, [_c(\"c1\", \"P1\", \"08:30\", 1), _c(\"c1\", \"P1\", \"08:30\", 1)])\n assert new[\"P1\"][\"version\"] == 2, \"the same change_id must be applied once\"\n assert list(conf) == [], \"a repeated change is not a conflict\"\ndef conflict(ns, out):\n new, conf = _r(ns)({\"P1\": {\"time\": \"09:00\", \"version\": 5}}, [_c(\"c1\", \"P1\", \"08:30\", 4)])\n assert new[\"P1\"] == {\"time\": \"09:00\", \"version\": 5}, \"a stale paper change must not overwrite the central record\"\n assert list(conf) == [\"c1\"], f\"expected conflict ['c1'], got {list(conf)}\"\ndef adds(ns, out):\n new, conf = _r(ns)({}, [_c(\"c1\", \"P9\", \"10:00\", 0)])\n assert new.get(\"P9\") == {\"time\": \"10:00\", \"version\": 1}, \"a pickup missing centrally should be added with version 1\"\ndef chain(ns, out):\n new, conf = _r(ns)({\"P1\": {\"time\": \"08:00\", \"version\": 3}}, [_c(\"c1\", \"P1\", \"08:30\", 3), _c(\"c2\", \"P1\", \"08:45\", 4)])\n assert new[\"P1\"] == {\"time\": \"08:45\", \"version\": 5}, f\"two successive changes should give version 5 at 08:45, got {new['P1']}\"\ndef pure(ns, out):\n central = {\"P1\": {\"time\": \"08:00\", \"version\": 1}}; log = [_c(\"c1\", \"P1\", \"08:30\", 1)]\n a, b = copy.deepcopy(central), copy.deepcopy(log)\n _r(ns)(central, log)\n assert central == a and log == b, \"reconcile must not modify its inputs\"\n\nCHECKS = [\n (\"a paper change is applied\", applies),\n (\"the same change is applied only once\", once),\n (\"a stale change becomes a conflict, not an overwrite\", conflict),\n (\"a missing pickup is added\", adds),\n (\"successive changes to one pickup are both applied\", chain),\n (\"the inputs are not modified\", pure),\n]\n", "mutants": "# Known bugs. Your own tests must catch each one (at least one of your tests fails).\nMUTANTS = [\n (\"last write wins: a stale paper change overwrites the central record\", '''\ndef reconcile(central, paper_log):\n new, seen = {k: dict(v) for k, v in central.items()}, set()\n for c in paper_log:\n if c[\"change_id\"] in seen:\n continue\n seen.add(c[\"change_id\"])\n cur = new.get(c[\"pickup_id\"], {\"version\": 0})\n new[c[\"pickup_id\"]] = {\"time\": c[\"time\"], \"version\": cur[\"version\"] + 1}\n return new, []\n'''),\n (\"a repeated change_id is applied again (and then reported as a conflict)\", '''\ndef reconcile(central, paper_log):\n new, conflicts = {k: dict(v) for k, v in central.items()}, []\n for c in paper_log:\n cur = new.get(c[\"pickup_id\"], {\"version\": 0})\n if cur[\"version\"] != c[\"base_version\"]:\n conflicts.append(c[\"change_id\"]); continue\n new[c[\"pickup_id\"]] = {\"time\": c[\"time\"], \"version\": cur[\"version\"] + 1}\n return new, conflicts\n'''),\n (\"the central list passed in is changed in place\", '''\ndef reconcile(central, paper_log):\n new, seen, conflicts = central, set(), []\n for c in paper_log:\n if c[\"change_id\"] in seen:\n continue\n seen.add(c[\"change_id\"])\n cur = new.get(c[\"pickup_id\"], {\"version\": 0})\n if cur[\"version\"] != c[\"base_version\"]:\n conflicts.append(c[\"change_id\"]); continue\n new[c[\"pickup_id\"]] = {\"time\": c[\"time\"], \"version\": cur[\"version\"] + 1}\n return new, conflicts\n'''),\n (\"a pickup missing from the central list is dropped\", '''\ndef reconcile(central, paper_log):\n new, seen, conflicts = {k: dict(v) for k, v in central.items()}, set(), []\n for c in paper_log:\n if c[\"change_id\"] in seen or c[\"pickup_id\"] not in new:\n continue\n seen.add(c[\"change_id\"])\n cur = new[c[\"pickup_id\"]]\n if cur[\"version\"] != c[\"base_version\"]:\n conflicts.append(c[\"change_id\"]); continue\n new[c[\"pickup_id\"]] = {\"time\": c[\"time\"], \"version\": cur[\"version\"] + 1}\n return new, conflicts\n'''),\n]\n", "title": "Build the reconciliation", "criterion": "RECOVER", "assignment": 5, "checkLabels": ["a paper change is applied", "the same change is applied only once", "a stale change becomes a conflict, not an overwrite", "a missing pickup is added", "successive changes to one pickup are both applied", "the inputs are not modified", "your own tests: at least three, all passing", "your tests catch every known bug (mutation test)"], "viva": ["Two paper changes to the same pickup were written from the same base version. Show what your code does and defend it.", "Delete your repeated-change test and run again. Which known bug survives, and what would it cost the transport desk?", "The paper log arrives out of order (c2 before c1). Show what happens and whether it is safe.", "Return conflicts with the pickup id as well as the change id. Change the code and one test."]}, "15": {"helpers": "# The supplied facts, exactly as in the scenario. Anything not listed is not known.\n# A: \"the required booking functions and an export API; its support commitment ends after two years\".\n# B: \"lacks recurring bookings and has no measured peak-load results\".\nFACTS = {\n (\"A\", \"booking\"): \"met\", (\"A\", \"recurring\"): \"met\", (\"A\", \"export-api\"): \"met\", (\"A\", \"support-3-years\"): \"gap\",\n (\"B\", \"booking\"): \"met\", (\"B\", \"recurring\"): \"gap\",\n}\nREQUIREMENTS = [\"booking\", \"recurring\", \"export-api\", \"support-3-years\", \"peak-load\"]\n", "starter": "# Assignment 6 build: an evidence-only fit check for the colleague's draft.\n# FACTS[(option, requirement)] is \"met\" or \"gap\", read only from the scenario. REQUIREMENTS lists what the pilot needs.\n# A requirement the facts do not settle is \"unknown\". Unknown never counts as met.\n\ndef fit(option, requirement):\n return FACTS.get((option, requirement), \"met\") # CHANGE ME: brochure reading\n\ndef shortlist(options):\n \"\"\"Options with no known gap and no unknown requirement.\"\"\"\n return [o for o in options if all(fit(o, r) != \"gap\" for r in REQUIREMENTS)] # CHANGE ME\n\ndef evidence_needed(option):\n \"\"\"The requirements still unknown for this option, in REQUIREMENTS order.\"\"\"\n return [] # CHANGE ME\n\n# Your tests: at least three functions whose names start with test_\n# The course also runs your tests against known buggy versions: each bug must make one of them fail.\ndef test_known_gap():\n assert fit(\"A\", \"support-3-years\") == \"gap\"\n", "checks": "def _g(ns, n):\n f = ns.get(n); assert callable(f), f\"define {n}\"\n return f\ndef known(ns, out):\n f = _g(ns, \"fit\")\n for k, v in FACTS.items():\n assert f(*k) == v, f\"fit{k} should be {v!r} from the facts\"\ndef unknown(ns, out):\n f = _g(ns, \"fit\")\n for o, r in [(\"A\", \"peak-load\"), (\"B\", \"export-api\"), (\"B\", \"support-3-years\"), (\"B\", \"peak-load\"), (\"C\", \"booking\")]:\n assert f(o, r) == \"unknown\", f\"fit({o!r}, {r!r}) is not settled by the facts: expected 'unknown', got {f(o, r)!r}\"\ndef short(ns, out):\n assert list(_g(ns, \"shortlist\")([\"A\", \"B\"])) == [], \"neither option is shown to meet every requirement, so the shortlist is empty\"\n assert list(_g(ns, \"shortlist\")([\"C\"])) == [], \"an option nobody has checked (no facts at all) must not be shortlisted\"\ndef needed(ns, out):\n f = _g(ns, \"evidence_needed\")\n for o, want in [(\"A\", [\"peak-load\"]), (\"B\", [\"export-api\", \"support-3-years\", \"peak-load\"])]:\n assert list(f(o)) == want, f\"evidence_needed({o!r}) should be {want}, got {list(f(o))}\"\n\nCHECKS = [\n (\"supplied facts are reported as given\", known),\n (\"unsettled requirements are unknown, never met\", unknown),\n (\"the shortlist needs evidence for every requirement\", short),\n (\"evidence_needed lists exactly the unknowns\", needed),\n]\n", "mutants": "# Known bugs. Your own tests must catch each one (at least one of your tests fails).\nMUTANTS = [\n (\"unknown counts as met (the brochure reading)\", '''\ndef fit(option, requirement):\n return FACTS.get((option, requirement), \"met\")\ndef shortlist(options):\n return [o for o in options if all(fit(o, r) == \"met\" for r in REQUIREMENTS)]\ndef evidence_needed(option):\n return [r for r in REQUIREMENTS if fit(option, r) == \"unknown\"]\n'''),\n (\"the shortlist removes known gaps only, so an option nobody has checked gets through\", '''\ndef fit(option, requirement):\n return FACTS.get((option, requirement), \"unknown\")\ndef shortlist(options):\n return [o for o in options if all(fit(o, r) != \"gap\" for r in REQUIREMENTS)]\ndef evidence_needed(option):\n return [r for r in REQUIREMENTS if fit(option, r) == \"unknown\"]\n'''),\n (\"evidence_needed also lists known gaps\", '''\ndef fit(option, requirement):\n return FACTS.get((option, requirement), \"unknown\")\ndef shortlist(options):\n return [o for o in options if all(fit(o, r) == \"met\" for r in REQUIREMENTS)]\ndef evidence_needed(option):\n return [r for r in REQUIREMENTS if fit(option, r) != \"met\"]\n'''),\n]\n", "their": "# Run 3 · their change (Assignment 6). The supplier ships version 2 of the component's facts:\n# the same names, but one promise is gone. Your code and tests are not changed.\nCHANGE = \"The supplier ships version 2 of its product facts. The names are the same, but the promise of an export API for option A is no longer stated.\"\nBEFORE = '(\"A\", \"export-api\"): \"met\",'\nAFTER = '(removed in version 2: the supplier no longer states this promise)'\n\ndef install(ns):\n facts = dict(ns[\"FACTS\"])\n facts.pop((\"A\", \"export-api\"), None)\n ns[\"FACTS\"] = facts\n\ndef scenario(ns):\n fit, need, short = ns[\"fit\"], ns[\"evidence_needed\"], ns[\"shortlist\"]\n f, e, s = fit(\"A\", \"export-api\"), need(\"A\"), short([\"A\", \"B\"])\n lines = [f\"fit('A', 'export-api') = {f!r}\", f\"evidence_needed('A') = {e!r}\", f\"shortlist(['A', 'B']) = {s!r}\"]\n correct = f == \"unknown\" and \"export-api\" in list(e) and \"A\" not in list(s)\n return lines, correct\n", "theirChange": "The supplier ships version 2 of its product facts. The names are the same, but the promise of an export API for option A is no longer stated.", "theirBefore": "(\"A\", \"export-api\"): \"met\",", "theirAfter": "(removed in version 2: the supplier no longer states this promise)", "title": "Build the evidence-only fit check", "criterion": "COMPARE", "assignment": 6, "checkLabels": ["supplied facts are reported as given", "unsettled requirements are unknown, never met", "the shortlist needs evidence for every requirement", "evidence_needed lists exactly the unknowns", "your own tests: at least three, all passing", "your tests catch every known bug (mutation test)"], "viva": ["The vendor sends a measured peak-load report for Option A that passes. Change one fact and show the new shortlist.", "Add a sixth requirement, “arabic-ui”, that nobody has checked. Which outputs change, and why do no facts change?", "Remove your test for an unchecked option and run again. Which known bug survives?", "Option B gets a written three-year support commitment. Change one fact: is B shortlisted now? Why not?"]}, "16": {"helpers": "class ContractError(Exception):\n \"\"\"Raise this when the component breaks its documented promise.\"\"\"\n\nclass FakeComponent:\n \"\"\"Behaves as documented: reserve(room_id, duration) with duration in seconds, 1..7200.\"\"\"\n def __init__(self, end_offset=0):\n self.calls, self.end_offset = [], end_offset\n def reserve(self, room_id, duration):\n self.calls.append((room_id, duration))\n if type(duration) is not int or not 1 <= duration <= 7200:\n raise ValueError(\"duration outside the documented contract\")\n return {\"id\": f\"R{len(self.calls)}\", \"start\": 0, \"end\": duration + self.end_offset}\n", "starter": "# Assignment 7 build: the adapter between the booking app (minutes) and the component (seconds).\n# component.reserve(room_id, duration) expects SECONDS, 1..7200, and returns\n# {\"id\": ..., \"start\": seconds, \"end\": seconds}. FakeComponent behaves exactly like that.\n# Precondition: minutes is a whole number from 1 to 120, else raise ValueError before calling.\n# Postcondition: end - start equals the requested seconds, else raise ContractError.\n\ndef reserve_minutes(component, room_id, minutes):\n return component.reserve(room_id, minutes) # CHANGE ME: today the adapter forwards unchanged\n\n# Your tests: at least three functions whose names start with test_\n# The course also runs your tests against known buggy versions: each bug must make one of them fail.\ndef test_thirty_minutes():\n c = FakeComponent()\n reserve_minutes(c, \"Lab-1\", 30)\n assert c.calls == [(\"Lab-1\", 1800)]\n", "checks": "def _a(ns):\n f = ns.get(\"reserve_minutes\"); assert callable(f), \"define reserve_minutes(component, room_id, minutes)\"\n return f\ndef converts(ns, out):\n c = FakeComponent(); r = _a(ns)(c, \"L1\", 30)\n assert c.calls == [(\"L1\", 1800)], f\"30 minutes must reach the component as 1800 seconds, got {c.calls}\"\n assert isinstance(r, dict) and r.get(\"id\"), \"return the component's reservation\"\ndef edges(ns, out):\n for m, s in [(1, 60), (120, 7200)]:\n c = FakeComponent(); _a(ns)(c, \"L1\", m)\n assert c.calls == [(\"L1\", s)], f\"{m} minutes must be accepted as {s} seconds\"\ndef rejects(ns, out):\n for bad in [0, 121, -5, \"30\", 2.5, True, None]:\n c = FakeComponent()\n try:\n _a(ns)(c, \"L1\", bad)\n except ValueError:\n assert not c.calls, f\"{bad!r} must be rejected before calling the component\"\n continue\n except Exception as e:\n raise AssertionError(f\"{bad!r} must raise ValueError, not {type(e).__name__}\")\n raise AssertionError(f\"{bad!r} must raise ValueError\")\ndef post(ns, out):\n c = FakeComponent(end_offset=5)\n try:\n _a(ns)(c, \"L1\", 30)\n except ContractError:\n return\n raise AssertionError(\"a reservation whose end time is wrong must raise ContractError\")\n\nCHECKS = [\n (\"minutes are converted to seconds\", converts),\n (\"1 and 120 minutes are accepted\", edges),\n (\"invalid minutes are rejected before the call\", rejects),\n (\"a wrong end time breaks the postcondition\", post),\n]\n", "mutants": "# Known bugs. Your own tests must catch each one (at least one of your tests fails).\nMUTANTS = [\n (\"minutes are forwarded without conversion\", '''\ndef reserve_minutes(component, room_id, minutes):\n if type(minutes) is not int or not 1 <= minutes <= 120:\n raise ValueError(\"minutes must be a whole number from 1 to 120\")\n r = component.reserve(room_id, minutes)\n if r[\"end\"] - r[\"start\"] != minutes:\n raise ContractError(\"wrong duration\")\n return r\n'''),\n (\"out-of-range minutes reach the component\", '''\ndef reserve_minutes(component, room_id, minutes):\n if type(minutes) is not int:\n raise ValueError(\"minutes must be a whole number\")\n r = component.reserve(room_id, minutes * 60)\n if r[\"end\"] - r[\"start\"] != minutes * 60:\n raise ContractError(\"wrong duration\")\n return r\n'''),\n (\"a fraction or True is accepted as minutes\", '''\ndef reserve_minutes(component, room_id, minutes):\n if not isinstance(minutes, (int, float)) or not 1 <= minutes <= 120:\n raise ValueError(\"minutes must be from 1 to 120\")\n r = component.reserve(room_id, int(minutes * 60))\n if r[\"end\"] - r[\"start\"] != int(minutes * 60):\n raise ContractError(\"wrong duration\")\n return r\n'''),\n (\"the postcondition is never checked\", '''\ndef reserve_minutes(component, room_id, minutes):\n if type(minutes) is not int or not 1 <= minutes <= 120:\n raise ValueError(\"minutes must be a whole number from 1 to 120\")\n return component.reserve(room_id, minutes * 60)\n'''),\n]\n", "their": "# Run 3 · their change (Assignment 7). The component team ships version 2: the same call,\n# reserve(room_id, duration), but duration is now in milliseconds. Your code and tests are not changed.\nCHANGE = \"The component team ships version 2. The call is the same, reserve(room_id, duration), but duration is now in milliseconds (1 to 7,200,000).\"\nBEFORE = 'reserve(room_id, duration) # duration in seconds, 1..7200'\nAFTER = 'reserve(room_id, duration) # duration in milliseconds, 1..7,200,000'\n\ndef install(ns):\n class FakeComponent:\n \"\"\"Version 2: reserve(room_id, duration) with duration in MILLISECONDS, 1..7,200,000.\"\"\"\n def __init__(self, end_offset=0):\n self.calls, self.end_offset = [], end_offset\n def reserve(self, room_id, duration):\n self.calls.append((room_id, duration))\n if type(duration) is not int or not 1 <= duration <= 7200000:\n raise ValueError(\"duration outside the documented contract\")\n return {\"id\": f\"R{len(self.calls)}\", \"start\": 0, \"end\": duration + self.end_offset}\n ns[\"FakeComponent\"] = FakeComponent\n\ndef scenario(ns):\n c = ns[\"FakeComponent\"]()\n try:\n ns[\"reserve_minutes\"](c, \"Lab-1\", 30)\n sent = c.calls[-1][1] if c.calls else None\n lines = [f\"reserve_minutes(component version 2, 'Lab-1', 30) sent duration = {sent!r}\",\n f\"component version 2 books {sent / 1000:g} seconds\" if isinstance(sent, int) else \"component version 2 was not called\"]\n except BaseException as e:\n sent = None\n lines = [f\"reserve_minutes(component version 2, 'Lab-1', 30) raised {type(e).__name__}: {e}\"]\n return lines, sent == 1800000\n", "theirChange": "The component team ships version 2. The call is the same, reserve(room_id, duration), but duration is now in milliseconds (1 to 7,200,000).", "theirBefore": "reserve(room_id, duration) # duration in seconds, 1..7200", "theirAfter": "reserve(room_id, duration) # duration in milliseconds, 1..7,200,000", "title": "Build the adapter", "criterion": "CONTRACT", "assignment": 7, "checkLabels": ["minutes are converted to seconds", "1 and 120 minutes are accepted", "invalid minutes are rejected before the call", "a wrong end time breaks the postcondition", "your own tests: at least three, all passing", "your tests catch every known bug (mutation test)"], "viva": ["The component now takes milliseconds (1 to 7,200,000). Change the adapter and say which tests change.", "The maximum becomes 90 minutes. Change one thing and show the test that proves it.", "The component may return an end time one second late. Keep or relax the postcondition? Defend it with a test.", "Remove the type check and run again. Which known bug is no longer caught?"]}, "17": {"helpers": "# store is a dict that survives restarts: a new BookingService(store) sees the same data.\n", "starter": "# Assignment 8 build: an idempotent booking service.\n# The app sends one request_id per user action and reuses it on every retry.\n# store survives restarts. reserve() must create at most one reservation per request_id\n# and return the same reservation id when the request is repeated. Reject a missing request_id.\n\nclass BookingService:\n def __init__(self, store):\n self.store = store\n store.setdefault(\"reservations\", {})\n\n def reserve(self, request_id, user, event):\n rid = f\"R{len(self.store['reservations']) + 1}\" # CHANGE ME: every call books again\n self.store[\"reservations\"][rid] = {\"user\": user, \"event\": event}\n return rid\n\n# Your tests: at least three functions whose names start with test_\n# The course also runs your tests against known buggy versions: each bug must make one of them fail.\ndef test_two_actions_two_bookings():\n s = BookingService({})\n assert s.reserve(\"a\", \"u1\", \"E1\") != s.reserve(\"b\", \"u1\", \"E1\")\n", "checks": "def _svc(ns, store):\n c = ns.get(\"BookingService\"); assert c is not None, \"define class BookingService\"\n return c(store)\ndef _count(store):\n return len(store.get(\"reservations\", {}))\ndef distinct(ns, out):\n st = {}; s = _svc(ns, st)\n a, b = s.reserve(\"a\", \"u1\", \"E1\"), s.reserve(\"b\", \"u1\", \"E1\")\n assert a != b and _count(st) == 2, \"two different request ids must give two reservations\"\ndef repeat(ns, out):\n st = {}; s = _svc(ns, st)\n a, b = s.reserve(\"a\", \"u1\", \"E1\"), s.reserve(\"a\", \"u1\", \"E1\")\n assert a == b, \"the same request id must return the same reservation id\"\n assert _count(st) == 1, f\"the same request id must create one reservation, found {_count(st)}\"\ndef lost(ns, out):\n st = {}; s = _svc(ns, st)\n s.reserve(\"x\", \"u1\", \"E1\") # the reply is lost\n rid = s.reserve(\"x\", \"u1\", \"E1\") # the app retries with the same id\n assert _count(st) == 1 and rid, \"a retry after a lost reply must not book twice\"\ndef restart(ns, out):\n st = {}\n first = _svc(ns, st).reserve(\"x\", \"u1\", \"E1\")\n again = _svc(ns, st).reserve(\"x\", \"u1\", \"E1\") # the service restarted\n assert again == first and _count(st) == 1, \"after a restart the same request id must return the first outcome\"\ndef missing(ns, out):\n s = _svc(ns, {})\n for bad in [None, \"\"]:\n try:\n s.reserve(bad, \"u1\", \"E1\")\n except ValueError:\n continue\n raise AssertionError(f\"request_id {bad!r} must raise ValueError\")\n\nCHECKS = [\n (\"different actions create different reservations\", distinct),\n (\"the same request id books once and returns the same id\", repeat),\n (\"a retry after a lost reply does not book twice\", lost),\n (\"the outcome survives a restart\", restart),\n (\"a missing request id is rejected\", missing),\n]\n", "mutants": "# Known bugs. Your own tests must catch each one (at least one of your tests fails).\nMUTANTS = [\n (\"every call books again\", '''\nclass BookingService:\n def __init__(self, store):\n self.store = store\n store.setdefault(\"reservations\", {})\n def reserve(self, request_id, user, event):\n if not request_id:\n raise ValueError(\"request_id is required\")\n rid = f\"R{len(self.store['reservations']) + 1}\"\n self.store[\"reservations\"][rid] = {\"user\": user, \"event\": event}\n return rid\n'''),\n (\"request ids are remembered in memory only, so a restart books again\", '''\nclass BookingService:\n def __init__(self, store):\n self.store, self.seen = store, {}\n store.setdefault(\"reservations\", {})\n def reserve(self, request_id, user, event):\n if not request_id:\n raise ValueError(\"request_id is required\")\n if request_id in self.seen:\n return self.seen[request_id]\n rid = f\"R{len(self.store['reservations']) + 1}\"\n self.store[\"reservations\"][rid] = {\"user\": user, \"event\": event}\n self.seen[request_id] = rid\n return rid\n'''),\n (\"a missing request id is accepted\", '''\nclass BookingService:\n def __init__(self, store):\n self.store = store\n store.setdefault(\"reservations\", {})\n store.setdefault(\"requests\", {})\n def reserve(self, request_id, user, event):\n if request_id in self.store[\"requests\"]:\n return self.store[\"requests\"][request_id]\n rid = f\"R{len(self.store['reservations']) + 1}\"\n self.store[\"reservations\"][rid] = {\"user\": user, \"event\": event}\n self.store[\"requests\"][request_id] = rid\n return rid\n'''),\n]\n", "their": "# Run 3 · their change (Assignment 8). The remote app ships a new release: after a lost reply it\n# sends the same booking again, but with a NEW request_id. Your code and tests are not changed.\nCHANGE = \"The remote app ships a new release. After a lost reply it sends the same booking again, but with a new request_id.\"\nBEFORE = 'retry after a lost reply: send the same request_id'\nAFTER = 'retry after a lost reply: send a new request_id'\n\ndef install(ns):\n pass # the remote app is not part of your code: only the scenario uses it\n\ndef scenario(ns):\n store = {}\n s = ns[\"BookingService\"](store)\n first = s.reserve(\"pay-7f3a\", \"u1\", \"E1\") # the reply to this call is lost\n second = s.reserve(\"pay-91c2\", \"u1\", \"E1\") # the remote app retries the same action with a new id\n n = len(store.get(\"reservations\", {})) if isinstance(store.get(\"reservations\"), dict) else None\n lines = [f\"first send (reply lost): reserve('pay-7f3a', 'u1', 'E1') = {first!r}\",\n f\"retry with a new request id: reserve('pay-91c2', 'u1', 'E1') = {second!r}\",\n f\"reservations in the store for this one user action: {n if n is not None else 'not readable'}\"]\n return lines, first == second\n", "theirChange": "The remote app ships a new release. After a lost reply it sends the same booking again, but with a new request_id.", "theirBefore": "retry after a lost reply: send the same request_id", "theirAfter": "retry after a lost reply: send a new request_id", "title": "Build the idempotent service", "criterion": "FAILURE", "assignment": 8, "checkLabels": ["different actions create different reservations", "the same request id books once and returns the same id", "a retry after a lost reply does not book twice", "the outcome survives a restart", "a missing request id is rejected", "your own tests: at least three, all passing", "your tests catch every known bug (mutation test)"], "viva": ["The store is wiped every night. Is your service still idempotent across midnight? Show it with a test.", "Two different users send the same request id. What does your service do, and should it?", "Remove the persistence of request ids and run again. Which test fails first, and which real failure is it?", "A retry arrives with a different event for the same request id. Change the code to refuse it, with a test."]}, "20": {"helpers": "LIMIT = 5 # proposed freshness limit, minutes\n", "starter": "# Assignment 9 build: honest feed display for the shared dashboard.\n# feed = {\"available\": True or False, \"updated\": minute of the last update, or None}\n# display_state: \"unavailable\" if the feed is unavailable, has no update time, or its update\n# time is in the future; \"stale\" if it is older than limit minutes; otherwise \"live\".\n# headline(states): \"All feeds live\" only when every feed is live; otherwise name every\n# feed that is not live with its state, for example \"Degraded: B stale, C unavailable\".\n\ndef display_state(feed, now, limit=LIMIT):\n return \"live\" # CHANGE ME: the proposed design shows every feed as live\n\ndef headline(states):\n return \"All feeds live\" # CHANGE ME\n\n# Your tests: at least three functions whose names start with test_\n# The course also runs your tests against known buggy versions: each bug must make one of them fail.\ndef test_fresh_feed_is_live():\n assert display_state({\"available\": True, \"updated\": 100}, now=102) == \"live\"\n", "checks": "def _g(ns, n):\n f = ns.get(n); assert callable(f), f\"define {n}\"\n return f\ndef live(ns, out):\n d = _g(ns, \"display_state\")\n assert d({\"available\": True, \"updated\": 100}, 102) == \"live\", \"a 2-minute-old available feed is live\"\n assert d({\"available\": True, \"updated\": 100}, 105) == \"live\", \"exactly at the limit is still live\"\ndef stale(ns, out):\n assert _g(ns, \"display_state\")({\"available\": True, \"updated\": 100}, 106) == \"stale\", \"older than the limit must be stale\"\ndef unavailable(ns, out):\n d = _g(ns, \"display_state\")\n for f, why in [({\"available\": False, \"updated\": 100}, \"an unavailable feed\"), ({\"available\": True, \"updated\": None}, \"a feed with no update time\"), ({\"available\": True, \"updated\": 120}, \"an update time in the future\")]:\n assert d(f, 110) == \"unavailable\", f\"{why} must be shown as unavailable\"\ndef limit_param(ns, out):\n assert _g(ns, \"display_state\")({\"available\": True, \"updated\": 100}, 108, limit=10) == \"live\", \"use the limit argument\"\ndef honest(ns, out):\n h = _g(ns, \"headline\")\n assert h({\"A\": \"live\", \"B\": \"live\"}) == \"All feeds live\", \"all live: say 'All feeds live'\"\n t = h({\"A\": \"live\", \"B\": \"stale\", \"C\": \"unavailable\"})\n assert \"All feeds live\" not in t, \"never claim all feeds live when one is not\"\n for part in [\"B\", \"stale\", \"C\", \"unavailable\"]:\n assert part in t, f\"the headline must name every feed that is not live with its state (missing {part!r})\"\n\nCHECKS = [\n (\"a fresh available feed is live\", live),\n (\"an old feed is stale\", stale),\n (\"unavailable, missing or future times show as unavailable\", unavailable),\n (\"the freshness limit is a parameter\", limit_param),\n (\"the headline never hides a degraded feed\", honest),\n]\n", "mutants": "# Known bugs. Your own tests must catch each one (at least one of your tests fails).\n_HEAD = '''\ndef headline(states):\n bad = [f\"{k} {v}\" for k, v in states.items() if v != \"live\"]\n return \"All feeds live\" if not bad else \"Degraded: \" + \", \".join(bad)\n'''\nMUTANTS = [\n (\"an unavailable feed keeps showing its last value\", '''\ndef display_state(feed, now, limit=LIMIT):\n u = feed.get(\"updated\")\n if u is None or u > now:\n return \"unavailable\"\n return \"stale\" if now - u > limit else \"live\"\n''' + _HEAD),\n (\"a feed exactly at the limit is shown as stale\", '''\ndef display_state(feed, now, limit=LIMIT):\n u = feed.get(\"updated\")\n if not feed.get(\"available\") or u is None or u > now:\n return \"unavailable\"\n return \"stale\" if now - u >= limit else \"live\"\n''' + _HEAD),\n (\"an update time in the future is shown as live\", '''\ndef display_state(feed, now, limit=LIMIT):\n u = feed.get(\"updated\")\n if not feed.get(\"available\") or u is None:\n return \"unavailable\"\n return \"stale\" if now - u > limit else \"live\"\n''' + _HEAD),\n (\"the headline hides a stale feed\", '''\ndef display_state(feed, now, limit=LIMIT):\n u = feed.get(\"updated\")\n if not feed.get(\"available\") or u is None or u > now:\n return \"unavailable\"\n return \"stale\" if now - u > limit else \"live\"\ndef headline(states):\n bad = [f\"{k} {v}\" for k, v in states.items() if v == \"unavailable\"]\n return \"All feeds live\" if not bad else \"Degraded: \" + \", \".join(bad)\n'''),\n]\n", "their": "# Run 3 · their change (Assignment 9). Three feeds, each passing its owner's own test. Owner B changes what\n# \"updated\" means without changing the feed version. Your code and tests are not changed.\nCHANGE = \"Owner B changes what \\\"updated\\\" means in feed B, without changing the version number (still v1). It now means minutes since the last update, not the minute of the last update.\"\nBEFORE = '\"updated\": minute of the last update # feed B, version v1'\nAFTER = '\"updated\": minutes since the last update # feed B, version still v1'\n\ndef install(ns):\n pass # the feeds are not part of your code: only the scenario uses them\n\ndef scenario(ns):\n now = 102\n feeds = {\"A\": {\"available\": True, \"updated\": 100}, \"B\": {\"available\": True, \"updated\": 100}, \"C\": {\"available\": True, \"updated\": 99}}\n states = {k: ns[\"display_state\"](f, now) for k, f in feeds.items()}\n head = ns[\"headline\"](states)\n lines = [\"each owner's own feed test passes: A, B and C\",\n \"feed B sends updated = 100, which now means 100 minutes since its last update\",\n f\"your display at minute {now}: \" + \", \".join(f\"{k} {v}\" for k, v in states.items()),\n f\"your headline: {head!r}\"]\n correct = states.get(\"B\") == \"stale\" and \"All feeds live\" not in str(head) and \"B\" in str(head)\n return lines, correct\n", "theirChange": "Owner B changes what \"updated\" means in feed B, without changing the version number (still v1). It now means minutes since the last update, not the minute of the last update.", "theirBefore": "\"updated\": minute of the last update # feed B, version v1", "theirAfter": "\"updated\": minutes since the last update # feed B, version still v1", "title": "Build the honest display", "criterion": "INTEGRATE", "assignment": 9, "checkLabels": ["a fresh available feed is live", "an old feed is stale", "unavailable, missing or future times show as unavailable", "the freshness limit is a parameter", "the headline never hides a degraded feed", "your own tests: at least three, all passing", "your tests catch every known bug (mutation test)"], "viva": ["The owners agree a 3-minute limit. Change one thing and show which of your tests changes.", "Add a fourth feed, parking, that is unavailable. What does the headline say? Show it with a test.", "The transport clock runs 2 minutes ahead. Which feed state changes, and is it honest?", "Change > to >= in the freshness rule and run again. Which known bug did you reintroduce?"]}};
var worker=null,runner=null;
function workerSource(){return "self.onmessage=async function(e){var m=e.data;try{if(!self.py){importScripts(m.base+'pyodide.js');var o={indexURL:m.base};if(m.std){var q=await fetch(m.base+m.std);if(!q.ok)throw Error('Python could not start: the standard library returned '+q.status);var a=new Uint8Array(await q.arrayBuffer());for(var i=0;i<a.length;i++)a[i]^=90;o.stdLibURL=URL.createObjectURL(new Blob([a],{type:'application/zip'}));}self.py=await loadPyodide(o);self.py.runPython(m.harness);}if(m.kind==='load'){postMessage({ok:true});return;}var their=m.kind==='their',f=self.py.globals.get(their?'__their':'__run');var r=their?f(m.helpers,m.student,m.their):f(m.helpers,m.student,m.checks,m.mutants||'');if(f.destroy)f.destroy();postMessage({ok:true,result:String(r)});}catch(err){postMessage({ok:false,error:String(err&&err.message||err)});}};";}
function call(msg,ms){return new Promise(function(resolve,reject){
if(!worker){try{worker=new Worker(URL.createObjectURL(new Blob([workerSource()],{type:'text/javascript'})));}catch(e){reject(Error('This browser cannot start Python here. Use a current Chrome, Edge, Firefox or Safari.'));return;}}
var w=worker,t=setTimeout(function(){w.terminate();if(worker===w)worker=null;reject(Error(msg.kind==='load'?'Python did not load in time. Check your internet connection and try again.':'Stopped after '+ms/1000+' seconds. Look for an endless loop, then run again.'));},ms);
w.onmessage=function(e){clearTimeout(t);if(e.data.ok){resolve(e.data.result);}else{w.terminate();if(worker===w)worker=null;reject(Error(e.data.error));}};
w.onerror=function(e){clearTimeout(t);w.terminate();if(worker===w)worker=null;reject(Error('Python could not start: '+(e.message||'check your internet connection')));};
w.postMessage(Object.assign({base:src,std:std,harness:HARNESS},msg));});}
function load(){return call({kind:'load'},120000).catch(function(e){if(!MIRROR||(src===MIRROR&&std))throw e;if(worker){try{worker.terminate();}catch(x){}worker=null;}if(src!==MIRROR)src=MIRROR;else std='python_stdlib.dat';return load();});}
function runOnce(ch,code){var s=SPEC[ch];return load().then(function(){return call({kind:'run',helpers:s.helpers,student:code,checks:s.checks,mutants:s.mutants||''},20000);}).then(JSON.parse);}
function runChecks(ch,code){var s=SPEC[ch];if(runner)return Promise.resolve(runner(ch,code,s));return runOnce(ch,code).catch(function(first){if(/^Stopped after /.test(first.message||''))throw first;if(worker){try{worker.terminate();}catch(e){}worker=null;}return runOnce(ch,code).catch(function(second){throw Error('Python build could not start after an automatic retry. Your code is preserved. '+(/Program terminated|did not load|could not start|fetch|network|importScripts/i.test(second.message||'')?'Python could not be downloaded or started on this device. Antivirus web protection, a network filter or a browser extension can block its files. Try another device (for example a lab computer), or another network, then run again. Details: ':'')+(second.message||second));});});}
function runTheir(ch,code){var s=SPEC[ch];if(runner)return Promise.resolve(runner(ch,code,s,'their'));return load().then(function(){return call({kind:'their',helpers:s.helpers,student:code,their:s.their},20000);}).then(JSON.parse);}
function encodeFacts(f){try{return 'their-v1:'+btoa(JSON.stringify(f));}catch(e){return '';}}
function fingerprint(text){try{if(typeof crypto!=='undefined'&&crypto.subtle&&typeof TextEncoder!=='undefined')return crypto.subtle.digest('SHA-256',new TextEncoder().encode(text)).then(function(b){return Array.from(new Uint8Array(b)).map(function(x){return x.toString(16).padStart(2,'0');}).join('');});}catch(e){}var h=2166136261;for(var i=0;i<text.length;i++){h^=text.charCodeAt(i);h=Math.imul(h,16777619)>>>0;}return Promise.resolve('fnv1a-'+h.toString(16));}
function lines(list){return list.map(function(r){return ' '+(r[1]?'✓ ':'✗ ')+r[0]+(r[1]||!r[2]?'':' · '+r[2]);}).join('\n');}
function runKey(ch,sid){return 'fbr:build-runs:ch'+ch+':'+String(sid||'').trim();}
/* A run counts as new when the code differs from the previous run: the coverage step needs a second run after one input changes. */
function nextRun(ch,sid,hash){var k=runKey(ch,sid),p=null;try{p=JSON.parse(localStorage.getItem(k)||'null');}catch(e){}var n=p&&p.n||0,prev=p&&p.hash||'',same=prev===hash;if(!same)n+=1;try{localStorage.setItem(k,JSON.stringify({n:n,hash:hash}));}catch(e){}return {n:n,prev:same?(p&&p.prev||''):prev,same:same};}
function runLine(run){return 'Run '+run.n+' on this device'+(run.same?' (same code as the previous run)':'')+(run.prev?' · previous code fingerprint: '+run.prev.slice(0,16):'')+'\n';}
function coverageText(r){var c=r.coverage;if(!c)return 'Coverage of your code by your own tests: not measured by this runner.\n';var gates=c.unit==='gates',miss=c.rows.filter(function(x){return !x[3]||!x[5];});return 'Coverage of your code by your own tests (course checks not counted): '+(gates?'gates evaluated '+c.lines[0]+' of '+c.lines[1]+' · gate outcomes ':'lines '+c.lines[0]+' of '+c.lines[1]+' · branches ')+c.branches[0]+' of '+c.branches[1]+'\n'+(gates&&!c.lines[0]&&c.lines[1]?' Your tests never evaluated a gate: call occurs(revised, {"event name", ...}) in a test.\n':'')+(miss.length?miss.map(function(x){var not=[];if(!x[3])not.push(x[2]);if(!x[5])not.push(x[4]);return ' '+(gates?'Outcome not seen':'Branch not taken')+': '+(typeof x[0]==='number'?'line '+x[0]:x[0])+' · '+x[1]+' · '+not.join(' and ');}).join('\n'):' Every branch was taken by your tests.')+(c.missed.length?'\n Lines never run by your tests: '+c.missed.join(', '):'')+'\n';}
function summary(r){if(r.error)return r.error;var t=r.tests.filter(function(x){return x[1];}).length,c=r.checks.filter(function(x){return x[1];}).length;return 'Your tests: '+t+' of '+r.tests.length+' pass · Course checks: '+c+' of '+r.checks.length+' pass';}
function vivaFor(ch,sid){var v=SPEC[ch].viva||[],h=2166136261,i;sid=String(sid||'').trim();if(!v.length||!sid)return '';for(i=0;i<sid.length;i++){h^=sid.charCodeAt(i);h=Math.imul(h,16777619)>>>0;}return v[(h+ch)%v.length];}
function report(ch,r,hash,sid,run){var q=vivaFor(ch,sid),head='BUILD RECORD · '+(SPEC[ch].assignment?'Assignment '+SPEC[ch].assignment+' (Chapter '+ch+')':'Practice (not assessed)')+' · '+new Date().toISOString()+'\nCode fingerprint: '+hash.slice(0,16)+' (the code above, exactly as run)\n'+(run?runLine(run):'')+(q?'Micro-viva change request (from Student ID '+String(sid).trim()+'): '+q+'\n':'');
if(r.error)return head+'Result: '+r.error+'\n';
return head+summary(r)+'\nYour tests:\n'+(r.tests.length?lines(r.tests):' (none found: name them test_…)')+'\nCourse checks:\n'+lines(r.checks)+'\n'+(r.mutants?'Known bugs your tests catch (mutation test):\n'+lines(r.mutants.map(function(m){return [m[0],m[1],m[1]?'':'not caught'];}))+'\n':'')+coverageText(r);}
function mount(ch){
var el=function(id){return document.getElementById(id);},code=el('labCases'),s=SPEC[ch];if(!code||!s)return;
var ev=el('labEvidence'),plan=el('labPrediction'),out=el('buildOutput'),run=el('buildRun'),reset=el('buildReset'),status=el('labStatus'),armed=false;
function changed(){ev.dispatchEvent(new Event('input',{bubbles:true}));}
var tf=el('theirFacts');function clearTheir(){if(tf&&tf.value!=='their-v0:run-3-not-recorded'){tf.value='their-v0:run-3-not-recorded';tf.dispatchEvent(new Event('input',{bubbles:true}));}}
if(!code.value.trim()&&!code.readOnly){code.value=s.starter;code.dispatchEvent(new Event('input',{bubbles:true}));}
out.textContent=ev.value?'Last recorded run:\n\n'+ev.value:'Write your plan, then run your tests and the course checks.';
if(ev.value)plan.readOnly=true;
code.addEventListener('keydown',function(e){if(e.key==='Tab'&&!e.shiftKey&&!code.readOnly){e.preventDefault();var a=code.selectionStart,b=code.selectionEnd;code.value=code.value.slice(0,a)+' '+code.value.slice(b);code.selectionStart=code.selectionEnd=a+4;code.dispatchEvent(new Event('input',{bubbles:true}));}});
code.addEventListener('input',function(){if(code.readOnly)return;if(plan.readOnly)plan.readOnly=false;clearTheir();if(ev.value){ev.value='';changed();out.textContent='Code changed: run again to record the new result.';}});
run.disabled=code.readOnly;if(reset)reset.disabled=code.readOnly;
run.addEventListener('click',function(){if(code.readOnly)return;
if(plan.value.trim().length<30){status.textContent='Write your build plan first (at least 30 characters): what your code will do and which cases your tests cover.';return;}
run.disabled=true;status.textContent=runner?'Running…':'Starting Python… the first run downloads it once (about 10 MB).';
var src=code.value;
runChecks(ch,src).then(function(r){return fingerprint(src).then(function(h){var sidEl=el('sid'),sid=sidEl?sidEl.value:'';if(code.value!==src)return;var rec=report(ch,r,h,sid,nextRun(ch,sid,h));clearTheir();ev.value=rec;changed();plan.readOnly=true;out.textContent=rec;status.textContent=summary(r)+'. Explain in your '+s.criterion+' what the build shows.';});})
.catch(function(e){if(/^Stopped after /.test(e&&e.message||'')){if(code.value===src){if(ev.value){ev.value='';changed();}out.textContent=e.message;status.textContent=e.message;}return;}/* an endless loop is a bug in the code, not a runner failure: no record */return fingerprint(src).then(function(h){var sidEl=el('sid'),r={error:'TECHNICAL RUNNER FAILURE: '+(e.message||e)},rec=report(ch,r,h,sidEl?sidEl.value:'');if(code.value!==src)return;ev.value=rec;changed();plan.readOnly=true;out.textContent=rec;status.textContent='Python runner failed after automatic recovery attempts. A technical-failure Build Record was created so Part A is not blocked; your instructor can review it.';});}).then(function(){run.disabled=code.readOnly;});});
var tr=el('theirRun'),ts=el('theirStatus');
if(tr&&s.their){tr.disabled=code.readOnly;tr.addEventListener('click',function(){if(code.readOnly)return;var rec=ev.value,m=rec.match(/Your tests: (\d+) of (\d+) pass/),n=+((rec.match(/^Run (\d+) on this device/m)||[])[1]||0);
if(!/^BUILD RECORD/.test(rec)||!m){ts.textContent='Run your tests first.';return;}
if(m[1]!==m[2]||+m[2]<3){ts.textContent='Make your own tests pass first (at least three). Then run their change without editing them.';return;}
if(n<2){ts.textContent='Do the coverage step first (Run 2). Then run their change.';return;}
var src=code.value;tr.disabled=true;ts.textContent='Running their change… your code and tests are not edited.';
runTheir(ch,src).then(function(r){return fingerprint(src).then(function(h){if(code.value!==src)return;if(r.error){ts.textContent=r.error;return;}
var pass=r.tests.filter(function(x){return x[1];}).length,green=r.tests.length>=3&&pass===r.tests.length,base=ev.value.split('\n\nTHEIR CHANGE · run 3')[0];
ev.value=base+'\n\nTHEIR CHANGE · run 3 · your code and tests unchanged (fingerprint '+h.slice(0,16)+')\nWhat changed (you did not write it): '+s.theirChange+'\n - '+s.theirBefore+'\n + '+s.theirAfter+'\nYour tests after their change: '+pass+' of '+r.tests.length+' pass\n'+lines(r.tests)+'\nWhat the shared case shows:\n'+(r.evidence||[]).map(function(x){return ' '+x;}).join('\n')+'\n';changed();
if(tf){tf.value=encodeFacts({h:h.slice(0,16),green:green,correct:r.correct===true});tf.dispatchEvent(new Event('input',{bubbles:true}));}
out.textContent=ev.value;ts.textContent='Their change is recorded. Now answer: did your tests stay green, and is the result still correct?';});})
.catch(function(e){ts.textContent=(e&&e.message)||String(e);}).then(function(){tr.disabled=code.readOnly;});});}
if(reset)reset.addEventListener('click',function(){if(code.readOnly)return;if(!armed){armed=true;reset.textContent='Click again to replace your code';setTimeout(function(){armed=false;reset.textContent='Reset to starter code';},4000);return;}armed=false;reset.textContent='Reset to starter code';plan.readOnly=false;code.value=s.starter;code.dispatchEvent(new Event('input',{bubbles:true}));});
}
return{SPEC:SPEC,mount:mount,runChecks:runChecks,vivaFor:vivaFor,coverageText:coverageText,nextRun:nextRun,setRunner:function(f){runner=f;}};
})();
if(typeof module!=='undefined'&&module.exports)module.exports=StudyBuild;
</script><script>StudyBuild.mount(0);</script></main><footer class="site-footer"><div class="site-footer-inner"><div><a class="footer-brand" href="iscarb.html">iSCARB</a><p>Software Engineering II · CPIT-455<br>Faculty of Computing & Information Technology<br>King Abdulaziz University · Jeddah, Saudi Arabia</p></div><div><h2>Explore the course</h2><a href="student-guide.html">Student guide</a><a href="course-resources.html">Course & sources</a><a href="nelc-alignment.html">Saudi alignment</a></div><div><h2>Teaching & collaboration</h2><a href="methodology.html">iSCARB methodology</a><a href="instructor-guide.html">Instructor guide</a><a href="index.html#evolution">Teaching history</a></div><div><h2>Professor Adeeb Noor</h2><p>Course instructor & iSCARB developer</p><a class="profile-link" href="https://adeebnoor.github.io/">Profile, research & contact ↗</a><a href="mailto:arnoor@kau.edu.sa">arnoor@kau.edu.sa</a></div></div><div class="site-footer-bottom"><span>Learning resources · Human judgment · Evidence</span><a href="#top">Back to top ↑</a></div></footer><script>/* open-on-link */(function(){function go(){var id=decodeURIComponent(location.hash.slice(1)),el=id&&document.getElementById(id);if(!el)return;for(var d=el.closest("details");d;d=d.parentElement&&d.parentElement.closest("details"))d.open=true;el.scrollIntoView();}addEventListener("hashchange",go);document.addEventListener("click",function(e){var a=e.target.closest&&e.target.closest("a[href*='#']");if(a&&a.hash===location.hash&&a.pathname===location.pathname)setTimeout(go,0);});if(document.readyState!=="loading")go();else addEventListener("DOMContentLoaded",go);})();</script></body></html>