#!/usr/bin/env python3 """Build the FC11 card source from the shared FreeRTOS C card vocabulary.""" import hashlib import json import subprocess import uuid from pathlib import Path ROOT = Path(__file__).resolve().parents[1] BASE = ROOT.parent / "lab-rv32i-freertos-c-notifications" / "json" / "card_source.json" def sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() def code_step(step_id, number, label, code_ref, evidence, strategy="code.storage"): return { "id": step_id, "number": number, "label": label, "mode": "CODE", "strategy_ref": strategy, "svg_label": f"{number:02d}", "code_ref": code_ref, "description": label, "evidence": evidence, } def run_step(step_id, number, label, event, code_ref, evidence, strategy="run.heap"): snapshot = { 1: "h0", 2: "h1", 3: "h2", 4: "dynamic-run", 5: "static-run", 6: "dynamic-callback", 7: "static-callback", 8: "pass", }[event] return { "id": step_id, "number": number, "label": label, "mode": "RUN", "event_id": f"E{event:02d}", "strategy_ref": strategy, "svg_label": f"{number:02d}", "code_ref": code_ref, "snapshot_ref": f"task01.{snapshot}", "description": label, "evidence": evidence, } def section(aid, label, title, content, asset, orientation, steps): width, height = (940, 560) if orientation == "landscape" else (660, 760) return { "title": f"{aid} — {label.title()}", "order": int(aid[1:]) * 10, "content_kind": "prose", "asset_page_mode": "one-per-page", "page_orientation": orientation, "content_tex": content, "assets": [{ "path": f"assets/{asset}.png", "html_path": f"assets/{asset}.svg", "source_path": f"assets/{asset}.puml", "caption": f"{aid} {label} — {title}.", "label": f"fig:{asset}", "alt": title, "kind": "diagram", "width": 1.0, "page_grid": { "columns": 1, "rows": 1, "page_width": width, "page_height": height, "overview": True, "step_tiles": {s["id"]: 1 for s in steps}, }, "interactive": { "kind": "uml-class", "storage_key": f"fc11-{asset}", "title": f"{aid} · {title}", "task": {"id": "task01", "label": "Task01 · two storage policies"}, "block": {"id": aid.lower(), "label": f"{aid} {label}", "description": content}, "phases": [{"id": label.lower(), "label": title.upper(), "steps": steps}], }, }], } data = json.loads(BASE.read_text()) card_uuid = str(uuid.uuid5(uuid.NAMESPACE_URL, "stem:freertos-c:fc11:static-allocation")) task_uuid = str(uuid.uuid5(uuid.NAMESPACE_URL, "stem:freertos-c:fc11:task01")) issued = "2026-07-19T00:00:00+02:00" repo_url = "http://77.90.8.171:3001/edu-inf/lab-rv32i-freertos-c-static-allocation" html_url = f"https://{card_uuid}/{task_uuid}" data["card"].update({ "id": "mpabi-freertos-c-11-static-allocation", "number": "11", "count": "15", "slug": "static-allocation", "title": "Static and dynamic allocation", "topic": "task, queue and timer with two storage policies", "level": "Rok 2 · L11 · RV32I/Hazard3", "revision_date": issued, "version": "v00.01", "uuid": card_uuid, }) data["title_block"].update({ "title": "Static and dynamic allocation", "url": html_url, "repository_url": repo_url, "url_host_uuid": card_uuid, "doc_uuid": task_uuid, "issued_on": issued, "series": "FREERTOS-C-11", }) data["front_page_scope"] = { "title": "Cel karty", "content_tex": "Uczeń rozdziela semantykę obiektu FreeRTOS od polityki przechowywania i dowodzi jej przez adresy oraz cztery pomiary wolnego heap.", "scope_title": "Zakres karty", "scope_content_tex": "Dynamiczny i statyczny task, queue oraz timer wykonują ten sam eksperyment. H1 jest mniejsze od H0, H2 równa się H1, a H3 równa się H2. Pamięć Idle i timer-service dostarcza aplikacja.", "scope_table": {"headers": ["Lekcja", "Task", "Idea", "Priorytet", "Status", "Version"], "rows": [{ "chapter": "FC11", "task": "Task01", "idea_tex": "behavior parity plus storage provenance", "priority": "główny", "status": "ready", "version": "v00.01", "key": True, }]}, } data["viewpoints"] = [ {"id": "A1", "label": "CONTEXT", "subtitle": "ownership boundaries", "status": "enabled", "target_label": "fig:a1-context"}, {"id": "A2", "label": "STRUCTURE", "subtitle": "dynamic/static object pairs", "status": "enabled", "target_label": "fig:a2-structure"}, {"id": "A3", "label": "DISPATCH", "subtitle": "same kernel dispatch", "status": "unavailable", "reason": "Storage policy does not change callback dispatch."}, {"id": "A4", "label": "APPLICATION", "subtitle": "behavior parity", "status": "enabled", "target_label": "fig:a4-application"}, {"id": "A5", "label": "FLOW", "subtitle": "H0/H1/H2/H3", "status": "enabled", "target_label": "fig:a5-flow"}, {"id": "A6", "label": "STATE", "subtitle": "covered by flow", "status": "unavailable", "reason": "No new lifecycle state machine; the card compares storage."}, {"id": "A7", "label": "RUNTIME", "subtitle": "address provenance", "status": "enabled", "target_label": "fig:a7-runtime"}, {"id": "A8", "label": "PATTERNS", "subtitle": "policy comparison", "status": "unavailable", "reason": "No wrapper pattern is introduced in this C card."}, ] data["debug_strategies"] = { "code.storage": { "id": "code.storage", "kind": "code", "action": "open", "expected_observations": ["persistent Static* buffers", "application static Idle/timer task memory"], "assertions": ["no local-lifetime storage", "both allocation modes enabled"], "evidence_fields": ["symbol", "section", "code_ref"], "prerequisites": ["FC11 source"], "layout": ["buffers", "create APIs", "FreeRTOSConfig"], "commands": ["rg -n 'x.*CreateStatic|vApplicationGet.*TaskMemory|Static(Task|Queue|Timer)_t' include src"], }, "run.heap": { "id": "run.heap", "kind": "run", "action": "replay", "expected_observations": ["H1 < H0", "H2 == H1", "H3 == H2"], "assertions": ["objects alive at H3", "no exact delta assumption"], "evidence_fields": ["H0", "H1", "H2", "H3", "timestamp"], "prerequisites": ["clean Hazard3 RAM"], "layout": ["heap values", "event order"], "commands": ["print g_fc11.heap_h0", "print g_fc11.heap_h1", "print g_fc11.heap_h2", "print g_fc11.heap_h3"], }, "run.parity": { "id": "run.parity", "kind": "run", "action": "replay", "expected_observations": ["both receive 0xCAFE", "both timers callback once"], "assertions": ["distinct stacks", "PASS and deterministic digest"], "evidence_fields": ["received", "timer calls", "SP", "digest"], "prerequisites": ["scheduler running"], "layout": ["two branches", "verifier"], "commands": ["print/x g_fc11.dynamic_received", "print/x g_fc11.static_received", "print g_fc11_pass"], }, } a1 = [ code_step("application", 1, "application owns experiment", "src/tasks/task01_static_allocation.c:20", "global experiment"), code_step("dynamic", 2, "dynamic APIs request storage", "src/tasks/task01_static_allocation.c:218", "xTaskCreate/xQueueCreate/xTimerCreate"), code_step("static", 3, "static APIs receive storage", "src/tasks/task01_static_allocation.c:239", "named buffers"), code_step("kernel", 4, "kernel owns object semantics", "vendor/FreeRTOS-Kernel/tasks.c:1", "same scheduler"), code_step("ram", 5, "both policies coexist in RAM", "src/tasks/task01_static_allocation.c:26", "ucHeap plus static buffers"), ] a2 = [ run_step("dynamic-task", 1, "dynamic task handle is in heap", 2, "src/tasks/task01_static_allocation.c:218", "heap address"), run_step("static-task", 2, "static task uses TCB and stack buffers", 3, "src/tasks/task01_static_allocation.c:239", "outside heap"), run_step("dynamic-queue", 3, "dynamic queue object is in heap", 2, "src/tasks/task01_static_allocation.c:228", "heap address"), run_step("static-queue", 4, "static queue uses supplied storage", 3, "src/tasks/task01_static_allocation.c:247", "outside heap"), run_step("dynamic-timer", 5, "dynamic timer object is in heap", 2, "src/tasks/task01_static_allocation.c:229", "heap address"), run_step("static-timer", 6, "static timer uses StaticTimer_t", 3, "src/tasks/task01_static_allocation.c:250", "outside heap"), code_step("services", 7, "Idle and timer-service storage is static", "src/tasks/task01_static_allocation.c:45", "two application callbacks"), ] a4 = [ run_step("dynamic-worker", 1, "dynamic branch receives message", 4, "src/tasks/task01_static_allocation.c:121", "0xCAFE", "run.parity"), run_step("static-worker", 2, "static branch receives message", 5, "src/tasks/task01_static_allocation.c:139", "0xCAFE", "run.parity"), run_step("daemon", 3, "daemon executes both callbacks", 7, "src/tasks/task01_static_allocation.c:99", "1 + 1", "run.parity"), run_step("verifier", 4, "verifier checks behavior parity", 8, "src/tasks/task01_static_allocation.c:157", "PASS", "run.parity"), ] a5 = [ run_step("h0", 1, "H0 baseline", 1, "src/tasks/task01_static_allocation.c:214", "baseline"), run_step("h1", 2, "dynamic objects make H1 smaller", 2, "src/tasks/task01_static_allocation.c:236", "H1 < H0"), run_step("h2", 3, "static objects leave heap unchanged", 3, "src/tasks/task01_static_allocation.c:263", "H2 = H1"), run_step("dynamic-run", 4, "dynamic worker runs", 4, "src/tasks/task01_static_allocation.c:121", "message" , "run.parity"), run_step("static-run", 5, "static worker runs", 5, "src/tasks/task01_static_allocation.c:139", "message", "run.parity"), run_step("dynamic-callback", 6, "dynamic timer callback", 6, "src/tasks/task01_static_allocation.c:99", "count 1", "run.parity"), run_step("static-callback", 7, "static timer callback", 7, "src/tasks/task01_static_allocation.c:99", "count 1", "run.parity"), run_step("pass", 8, "H3 unchanged and verifier passes", 8, "src/tasks/task01_static_allocation.c:157", "H3 = H2", "run.parity"), ] a7 = [ run_step("heap-arena", 1, "dynamic handles lie in ucHeap", 8, "src/tasks/task01_static_allocation.c:174", "three true predicates"), run_step("named-buffers", 2, "static handles lie outside ucHeap", 8, "src/tasks/task01_static_allocation.c:177", "three false predicates"), code_step("service-buffers", 3, "kernel-service buffers are named", "src/tasks/task01_static_allocation.c:34", "Idle and daemon buffers"), run_step("heap-relation", 4, "four checkpoints satisfy relation", 8, "include/task01_static_allocation_model.h:34", "H1