feat: publish A1-A8 card viewer and FreeRTOS C generator
Check card layouts / check (push) Has been cancelled
Check card layouts / check (push) Has been cancelled
This commit is contained in:
+56
-4
@@ -45,6 +45,17 @@ której powstaje `https://<url_host_uuid>/<doc_uuid>`.
|
|||||||
`show_repository_qr: true` wymaga `repository_url`. Adres podglądu `localhost`
|
`show_repository_qr: true` wymaga `repository_url`. Adres podglądu `localhost`
|
||||||
nie powinien być publikowany na karcie.
|
nie powinien być publikowany na karcie.
|
||||||
|
|
||||||
|
### Publikacja lokalna i globalna
|
||||||
|
|
||||||
|
UUID hosta/workspace oraz UUID karty są stabilną tożsamością zasobu, a nie
|
||||||
|
tożsamością konkretnego serwera. Viewer podczas zajęć otwiera kartę przez
|
||||||
|
względny adres lokalnego ingressu K3s; kanoniczny adres globalny pozostaje w
|
||||||
|
metadanych, QR i udostępnianych odsyłaczach. Utrata Internetu nie może blokować
|
||||||
|
katalogu, przełączania kart, API sterowania, symulatora, dowodów ani raportu.
|
||||||
|
|
||||||
|
Pełny kontrakt topologii, routingu, magazynowania i przyszłej synchronizacji
|
||||||
|
opisuje [OFFLINE-FIRST-DEPLOYMENT.md](OFFLINE-FIRST-DEPLOYMENT.md).
|
||||||
|
|
||||||
### Status i wersja tasków w zakresie karty
|
### Status i wersja tasków w zakresie karty
|
||||||
|
|
||||||
Wiersze `front_page_scope.scope_table.rows[]` mogą opisywać stan opracowania
|
Wiersze `front_page_scope.scope_table.rows[]` mogą opisywać stan opracowania
|
||||||
@@ -180,10 +191,51 @@ rozmiaru:
|
|||||||
|
|
||||||
Jeśli każda figura ma odpowiadać osobnej stronie wydruku, sekcja deklaruje
|
Jeśli każda figura ma odpowiadać osobnej stronie wydruku, sekcja deklaruje
|
||||||
`"asset_page_mode": "one-per-page"`. Generator wtedy wstawia jawne podziały
|
`"asset_page_mode": "one-per-page"`. Generator wtedy wstawia jawne podziały
|
||||||
stron w TeX i buduje osobne płótna A4 w React/HTML. Pierwsza strona zachowuje
|
stron w TeX i buduje osobne płótna A4 w React/HTML. Diagram interaktywny sam
|
||||||
nagłówek sekcji, a kolejne są stronami kontynuacji z powtórzoną tabliczką
|
renderuje zwarty, jednakowy nagłówek na każdej części; zewnętrzny nagłówek
|
||||||
zasobu. Widok ekranowy rozdziela płótna odstępem `5cm`; reguła nie wpływa na
|
sekcji nie przesuwa pierwszego kafla względem pozostałych.
|
||||||
wydruk.
|
|
||||||
|
Orientację wybiera sekcja przez `"page_orientation": "portrait"` albo
|
||||||
|
`"landscape"`. Jest to własność fizycznej strony, a nie zamiennik dla
|
||||||
|
`page_grid`: pojedynczy szeroki diagram może dostać landscape, pionowa
|
||||||
|
hierarchia portrait, a dopiero model nieczytelny po właściwym dobraniu
|
||||||
|
orientacji otrzymuje jawne złożenie kilku stron.
|
||||||
|
|
||||||
|
Diagram większy od bezpiecznego pola A4 deklaruje `page_grid` przy assetcie:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"page_grid": {
|
||||||
|
"columns": 2,
|
||||||
|
"rows": 2,
|
||||||
|
"cuts_x": [660],
|
||||||
|
"cuts_y": [440],
|
||||||
|
"page_width": 660,
|
||||||
|
"page_height": 760,
|
||||||
|
"step_tiles": {"class-base": 1, "runtime-proof": 4}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`cuts_x` i `cuts_y` są współrzędnymi SVG, a `step_tiles` przypisuje każdy krok
|
||||||
|
interaktywny dokładnie do jednego fizycznego kafla (numeracja wierszowa od 1).
|
||||||
|
Generator odrzuca kafel większy od pola A4 oraz szew przecinający zamknięty
|
||||||
|
blok UML. React może złożyć strony w `spread_group`, zachowując jeden kursor i
|
||||||
|
jedną numerację rysunku; wydruk nadal zawiera niezależne kartki A4.
|
||||||
|
|
||||||
|
Włączony spread jest automatycznie dopasowywany do szerokości widoku, tak aby
|
||||||
|
wszystkie strony jednego wiersza i ich łączenia były widoczne równocześnie.
|
||||||
|
Nagłówki stron pokazują pulsujące kierunki kontynuacji `← ↑ ↓ →`; środkowa
|
||||||
|
strona szeregu może wskazywać obie strony, a złożenie wielowierszowe także górę
|
||||||
|
i dół. Wskaźniki są wyłącznie nawigacją ekranową i znikają w druku.
|
||||||
|
|
||||||
|
### Boczne szyny paneli
|
||||||
|
|
||||||
|
Narzędzia pomocnicze HTML używają wspólnego systemu paneli bocznych zamiast
|
||||||
|
modalnej zasłony. Cienka kolorowa szyna otwiera panel w trybie `Auto` po
|
||||||
|
najechaniu; kliknięcie przełącza go w `Fixed`. Kolejny klik, przycisk zamknięcia
|
||||||
|
albo `Esc` odpina panel. Renderer może wystawić kilka niezależnych szyn po obu
|
||||||
|
stronach (obecnie: katalog i spis treści po lewej, przebieg i raport po prawej).
|
||||||
|
Panele nakładają się na widok tylko podczas pracy i są zawsze usuwane z
|
||||||
|
wydruku. Nie zmieniają geometrii stron A4 ani kafelków diagramu.
|
||||||
|
|
||||||
Końcowy słownik jest domyślnie obecny w React/HTML dla zgodności. Karta, która
|
Końcowy słownik jest domyślnie obecny w React/HTML dla zgodności. Karta, która
|
||||||
ma zawierać wyłącznie jawnie wymienione strony, ustawia na poziomie głównym
|
ma zawierać wyłącznie jawnie wymienione strony, ustawia na poziomie głównym
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
# Offline-first local and global deployment
|
||||||
|
|
||||||
|
## Architecture decision
|
||||||
|
|
||||||
|
The teaching application must remain fully operational when the Internet
|
||||||
|
connection is unavailable. The local K3s cluster is therefore the primary
|
||||||
|
runtime during a lesson. The global installation is a replica, publication
|
||||||
|
endpoint and later synchronization peer; it is not a runtime dependency of a
|
||||||
|
lesson.
|
||||||
|
|
||||||
|
The development server on `http://localhost:8080` represents the local ingress
|
||||||
|
contract. Browser code must use relative, same-origin application and API URLs
|
||||||
|
so that the same generated bundle can run behind the development server, local
|
||||||
|
K3s ingress or the global ingress without rebuilding the card.
|
||||||
|
|
||||||
|
## Stable identity is not a transport address
|
||||||
|
|
||||||
|
A resource is identified by stable values:
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace/site UUID + card UUID + version + content digest
|
||||||
|
```
|
||||||
|
|
||||||
|
These values must not change when the resource moves between local and global
|
||||||
|
deployments. A URL is only one endpoint resolving that identity.
|
||||||
|
|
||||||
|
The target catalogue contract is:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"site_uuid": "<uuid-1>",
|
||||||
|
"card_uuid": "<uuid-2>",
|
||||||
|
"version": "v00.04",
|
||||||
|
"digest": "sha256:..."
|
||||||
|
},
|
||||||
|
"endpoints": {
|
||||||
|
"local": "/cards/<uuid-1>/<uuid-2>/",
|
||||||
|
"canonical": "https://<public-origin>/<uuid-1>/<uuid-2>/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The internal card tree navigates to `endpoints.local`. The canonical endpoint
|
||||||
|
is retained for publication metadata, QR codes, external sharing and global
|
||||||
|
synchronization. It must not be the only way to open another card from a local
|
||||||
|
viewer.
|
||||||
|
|
||||||
|
An installation may additionally use split-horizon DNS so the canonical host
|
||||||
|
resolves to the local ingress on the school network and to the global ingress
|
||||||
|
outside it. Correctness must not depend on split DNS, however: the relative
|
||||||
|
local route remains the explicit offline path.
|
||||||
|
|
||||||
|
## Local K3s runtime
|
||||||
|
|
||||||
|
The local cluster owns every service required during a lesson:
|
||||||
|
|
||||||
|
```text
|
||||||
|
browser
|
||||||
|
-> local DNS / K3s ingress
|
||||||
|
-> card catalogue and resource resolver
|
||||||
|
-> generated HTML, SVG, TeX and downloadable artefacts
|
||||||
|
-> card control API and navigation state
|
||||||
|
-> Neovim / simulator bridge
|
||||||
|
-> progress, event outbox and evidence storage
|
||||||
|
-> local Gitea and container registry where the lesson needs them
|
||||||
|
```
|
||||||
|
|
||||||
|
No lesson-critical asset may be loaded at runtime from a CDN, public font
|
||||||
|
service, GitHub, a public package registry or the global STEM instance.
|
||||||
|
Dependencies, container images and generated card releases are synchronized to
|
||||||
|
the local cluster before the lesson.
|
||||||
|
|
||||||
|
Recommended K3s responsibilities are:
|
||||||
|
|
||||||
|
- Traefik or an equivalent ingress as the single browser entry point;
|
||||||
|
- a catalogue/resolver mapping resource UUIDs to local immutable releases;
|
||||||
|
- local object storage for generated artefacts, screenshots and reports;
|
||||||
|
- a durable database for catalogue, progress and synchronization metadata;
|
||||||
|
- persistent volumes for card state, evidence and the event outbox;
|
||||||
|
- local DNS and pre-provisioned TLS material;
|
||||||
|
- a local OCI registry and Gitea mirror for reproducible laboratory work.
|
||||||
|
|
||||||
|
A browser service worker may cache the application shell and the last opened
|
||||||
|
cards, but it is an additional resilience layer. It does not replace the local
|
||||||
|
server, control API or durable K3s storage.
|
||||||
|
|
||||||
|
## Global runtime
|
||||||
|
|
||||||
|
The global deployment exposes the same resource identity and compatible API
|
||||||
|
schemas. It provides:
|
||||||
|
|
||||||
|
- externally reachable published cards and QR targets;
|
||||||
|
- long-term storage of accepted reports and evidence;
|
||||||
|
- upstream card releases and catalogue metadata;
|
||||||
|
- cross-site administration and backup;
|
||||||
|
- the synchronization peer for one or more local teaching sites.
|
||||||
|
|
||||||
|
Local and global installations may have different origins. Generated cards
|
||||||
|
must therefore contain no environment-specific API origin in JavaScript.
|
||||||
|
|
||||||
|
## Navigation contract
|
||||||
|
|
||||||
|
Clicking a card in the tree follows this policy:
|
||||||
|
|
||||||
|
1. resolve its stable resource identity in the local catalogue;
|
||||||
|
2. open the relative local route on the current origin;
|
||||||
|
3. use a locally cached immutable release when the active release is already
|
||||||
|
present but the catalogue backend is temporarily restarting;
|
||||||
|
4. offer the canonical global endpoint only as an explicit fallback when a
|
||||||
|
network is available;
|
||||||
|
5. show a clear `PLAN / no release` state when no local or global release
|
||||||
|
exists—never silently open an unrelated card.
|
||||||
|
|
||||||
|
Changing cards must not require starting another development HTTP server or
|
||||||
|
changing ports. One ingress serves the catalogue and routes all card releases.
|
||||||
|
|
||||||
|
## Synchronization boundary
|
||||||
|
|
||||||
|
Synchronization is deliberately separate from lesson execution. Its data model
|
||||||
|
must be designed now even if the transport is implemented later.
|
||||||
|
|
||||||
|
Card releases are immutable and content-addressed. They normally synchronize
|
||||||
|
from global to local. Classroom work synchronizes from local to global through
|
||||||
|
an append-only outbox containing globally unique, idempotent events:
|
||||||
|
|
||||||
|
```text
|
||||||
|
event_id, site_id, actor_id, resource identity, local sequence,
|
||||||
|
recorded_at, payload schema, payload digest
|
||||||
|
```
|
||||||
|
|
||||||
|
Progress changes, navigation checkpoints, screenshots, report annotations and
|
||||||
|
final report submissions are separate event types. Retrying a transfer cannot
|
||||||
|
duplicate an event. Binary evidence is uploaded by digest and referenced from
|
||||||
|
events.
|
||||||
|
|
||||||
|
Conflict rules are explicit:
|
||||||
|
|
||||||
|
- immutable card releases never merge; a new digest is a new release;
|
||||||
|
- append-only evidence never overwrites another site event;
|
||||||
|
- mutable progress uses its event history and actor/site identity rather than
|
||||||
|
blind last-write-wins;
|
||||||
|
- catalogue activation is an administrative decision with a recorded base
|
||||||
|
revision;
|
||||||
|
- rejected or incompatible events remain in a visible quarantine queue.
|
||||||
|
|
||||||
|
The local installation displays synchronization health but never blocks a
|
||||||
|
lesson because the global peer is offline.
|
||||||
|
|
||||||
|
## Offline acceptance test
|
||||||
|
|
||||||
|
A release is ready for classroom use only when the following test passes:
|
||||||
|
|
||||||
|
1. preload the selected card series, images and containers into local K3s;
|
||||||
|
2. disconnect the Internet uplink while preserving the classroom LAN;
|
||||||
|
3. open the catalogue through the local ingress;
|
||||||
|
4. switch between all released cards without leaving the local origin;
|
||||||
|
5. run Neovim, simulator checkpoints and diagram navigation;
|
||||||
|
6. record progress, timestamps, screenshots and a report;
|
||||||
|
7. restart the browser and one application pod and recover the saved state;
|
||||||
|
8. reconnect the uplink and verify idempotent synchronization without data
|
||||||
|
loss or duplicated evidence.
|
||||||
|
|
||||||
|
Passing only an online browser test is not sufficient.
|
||||||
@@ -0,0 +1,725 @@
|
|||||||
|
# A1–A8 Interactive Architecture Viewpoints
|
||||||
|
|
||||||
|
Status: design contract, version 0.2, 2026-07-18.
|
||||||
|
|
||||||
|
This document defines how STEM cards describe architecture, code and runtime
|
||||||
|
evidence. It is a house convention, not a new UML standard. The convention is
|
||||||
|
organized in the spirit of ISO/IEC/IEEE 42010:2022, uses UML 2.5.1 notation,
|
||||||
|
adds selected MARTE 1.3 concepts for real-time systems, and uses PlantUML as
|
||||||
|
the textual source of rendered diagrams.
|
||||||
|
|
||||||
|
## Scope and rollout
|
||||||
|
|
||||||
|
A1–A8 is runtime-independent. The fixed viewpoints are intended to support
|
||||||
|
embedded RTOS software, ordinary C++ object models, POSIX or standard threads,
|
||||||
|
and asynchronous Boost.Asio applications without changing the navigation
|
||||||
|
grammar.
|
||||||
|
|
||||||
|
The rollout is deliberately staged:
|
||||||
|
|
||||||
|
1. **current application:** FreeRTOS C++ cards on RV32I/Hazard3;
|
||||||
|
2. **later application:** `std::thread`/`std::jthread` and Boost.Asio;
|
||||||
|
3. **not yet scheduled:** concrete Asio/thread card repositories and lesson
|
||||||
|
numbering.
|
||||||
|
|
||||||
|
The later profile is specified below so current diagrams do not bake in RTOS-
|
||||||
|
only assumptions. It is a compatibility target, not a claim that the current
|
||||||
|
FreeRTOS card already implements or teaches Asio.
|
||||||
|
|
||||||
|
## Standards boundary
|
||||||
|
|
||||||
|
- **ISO/IEC/IEEE 42010:2022** supplies the concepts of architecture
|
||||||
|
descriptions, viewpoints, views and model kinds. A1–A8 is our concrete set
|
||||||
|
of viewpoints. We do not claim full ISO conformance merely because a card
|
||||||
|
uses these labels.
|
||||||
|
- **UML 2.5.1** supplies the notation for classes, objects, components,
|
||||||
|
interactions, activities and state machines. It does not prescribe A1–A8.
|
||||||
|
- **MARTE 1.3** may annotate schedulable resources, time, hardware and
|
||||||
|
allocation in RTOS cards. A MARTE property must come from configuration,
|
||||||
|
code or measurement; authors must not invent period, deadline or execution
|
||||||
|
time merely to fill a box.
|
||||||
|
- **arc42** is a useful wider documentation template. A1–A8 is the smaller,
|
||||||
|
interactive teaching slice used inside a card, not a replacement for an
|
||||||
|
architecture description of the whole product.
|
||||||
|
- **PlantUML** is the text-first renderer. The `.puml` source, not a manually
|
||||||
|
edited SVG, is authoritative.
|
||||||
|
|
||||||
|
Primary references:
|
||||||
|
|
||||||
|
- <https://www.iso.org/standard/74393.html>
|
||||||
|
- <https://www.omg.org/spec/UML/2.5.1>
|
||||||
|
- <https://www.omg.org/spec/MARTE/1.3>
|
||||||
|
- <https://arc42.org/overview>
|
||||||
|
- <https://plantuml.com/class-diagram>
|
||||||
|
- <https://plantuml.com/sequence-diagram>
|
||||||
|
- <https://plantuml.com/ascii-art>
|
||||||
|
|
||||||
|
## The fixed navigation model
|
||||||
|
|
||||||
|
The tab names remain stable across cards. A subtitle identifies the actual
|
||||||
|
mechanism being taught.
|
||||||
|
|
||||||
|
```text
|
||||||
|
A1 CONTEXT
|
||||||
|
A2 STRUCTURE
|
||||||
|
A3 DISPATCH
|
||||||
|
A4 APPLICATION
|
||||||
|
A5 FLOW
|
||||||
|
A6 STATE
|
||||||
|
A7 RUNTIME
|
||||||
|
A8 PATTERNS
|
||||||
|
```
|
||||||
|
|
||||||
|
These eight tabs ask eight different questions:
|
||||||
|
|
||||||
|
| ID | Question | Typical model kind |
|
||||||
|
|---|---|---|
|
||||||
|
| A1 CONTEXT | Where is the subject and what lies outside its boundary? | component, package or deployment view |
|
||||||
|
| A2 STRUCTURE | What static elements and relationships construct it? | class, component or object structure |
|
||||||
|
| A3 DISPATCH | How is an abstract call bound to the actual implementation? | class plus sequence/call-path view |
|
||||||
|
| A4 APPLICATION | How does application code specialize or compose it? | application class/object view |
|
||||||
|
| A5 FLOW | What happens in what order? | sequence or activity view |
|
||||||
|
| A6 STATE | Which lifecycles and state transitions exist? | state machine, never an unlabeled flowchart |
|
||||||
|
| A7 RUNTIME | What actually exists in memory and in the debugger now? | object diagram plus evidence overlay |
|
||||||
|
| A8 PATTERNS | Which design roles and trade-offs explain the construction? | participant map plus decision record |
|
||||||
|
|
||||||
|
Not every card needs all eight views. The top-level navigation keeps all eight
|
||||||
|
positions stable; an omitted view is visibly unavailable and states why. A
|
||||||
|
small card should not create decorative diagrams to fill empty tabs.
|
||||||
|
|
||||||
|
## Viewpoint contracts
|
||||||
|
|
||||||
|
### A1 CONTEXT
|
||||||
|
|
||||||
|
**Purpose:** establish system boundary and dependency direction before class
|
||||||
|
details appear.
|
||||||
|
|
||||||
|
Required content:
|
||||||
|
|
||||||
|
- subject under study;
|
||||||
|
- upstream application actor or component;
|
||||||
|
- wrapped platform/API;
|
||||||
|
- runtime platform, hardware or simulator when relevant;
|
||||||
|
- one verb on every boundary-crossing relation.
|
||||||
|
|
||||||
|
For an RTOS wrapper this normally reads:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Application task
|
||||||
|
│ specializes / uses
|
||||||
|
▼
|
||||||
|
C++ wrapper
|
||||||
|
│ calls
|
||||||
|
▼
|
||||||
|
FreeRTOS C API
|
||||||
|
│ schedules on
|
||||||
|
▼
|
||||||
|
Hazard3 simulator
|
||||||
|
```
|
||||||
|
|
||||||
|
A1 must not list every method. It is a map, not a class reference.
|
||||||
|
|
||||||
|
### A2 STRUCTURE
|
||||||
|
|
||||||
|
**Purpose:** show the static construction that the compiler sees.
|
||||||
|
|
||||||
|
Required content:
|
||||||
|
|
||||||
|
- class/interface/component names;
|
||||||
|
- relevant fields and operations only;
|
||||||
|
- visibility where it is part of the lesson;
|
||||||
|
- precise relationship type: generalization, realization, composition,
|
||||||
|
aggregation or dependency;
|
||||||
|
- storage/ownership role when the structure owns a resource.
|
||||||
|
|
||||||
|
Class rows receive semantic anchors only when a card can connect them to a
|
||||||
|
source line or a compile-time proof. Inheritance, `friend`, templates and
|
||||||
|
visibility are CODE facts; they are never presented as runtime events.
|
||||||
|
|
||||||
|
### A3 DISPATCH
|
||||||
|
|
||||||
|
**Purpose:** explain the binding mechanism between the generic boundary and
|
||||||
|
the concrete implementation.
|
||||||
|
|
||||||
|
The title is fixed; the subtitle is mechanism-specific. Examples:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A3 DISPATCH · CRTP static dispatch
|
||||||
|
A3 DISPATCH · virtual dispatch through vtable
|
||||||
|
A3 DISPATCH · C callback and function pointer
|
||||||
|
A3 DISPATCH · event to subscriber
|
||||||
|
A3 DISPATCH · executor and completion handler
|
||||||
|
```
|
||||||
|
|
||||||
|
The diagram must identify:
|
||||||
|
|
||||||
|
1. the call origin;
|
||||||
|
2. the erased or abstract boundary;
|
||||||
|
3. the cast, lookup or binding operation;
|
||||||
|
4. the concrete target;
|
||||||
|
5. whether the decision is compile-time or runtime;
|
||||||
|
6. the ABI proof that distinguishes the mechanism.
|
||||||
|
|
||||||
|
This viewpoint prevents a wrapper from being mislabeled. A CRTP wrapper must
|
||||||
|
show `static_cast<Derived*>` and the absence of virtual dispatch. A dynamic
|
||||||
|
wrapper must show object address, `vptr`, vtable entry and indirect call.
|
||||||
|
|
||||||
|
### A4 APPLICATION
|
||||||
|
|
||||||
|
**Purpose:** show one concrete use without repeating the complete library.
|
||||||
|
|
||||||
|
The generic mechanism is collapsed to its public contract. The view maps every
|
||||||
|
pattern or framework role to the names used in the exercise, for example
|
||||||
|
`Strategy -> PaymentStrategy` and `Context -> ShoppingCart`.
|
||||||
|
|
||||||
|
At least one application invariant is printed next to the model. Example:
|
||||||
|
"`ShoppingCart` depends on the strategy interface, not a payment variant."
|
||||||
|
|
||||||
|
### A5 FLOW
|
||||||
|
|
||||||
|
**Purpose:** show causal order for one named scenario.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- state the precondition and observable postcondition;
|
||||||
|
- use sequence diagrams for collaborating participants and activity diagrams
|
||||||
|
for algorithmic branching;
|
||||||
|
- number messages in the same order as interactive steps;
|
||||||
|
- show returns only when they carry teaching information;
|
||||||
|
- split construction, scheduling and callback dispatch into phases when one
|
||||||
|
page would hide the boundary.
|
||||||
|
|
||||||
|
A5 is the primary home of RUN steps and deterministic checkpoints.
|
||||||
|
|
||||||
|
### A6 STATE
|
||||||
|
|
||||||
|
**Purpose:** separate lifecycle from execution flow.
|
||||||
|
|
||||||
|
When two state domains exist, draw two explicit lanes rather than merging them.
|
||||||
|
For a FreeRTOS C++ task these are commonly:
|
||||||
|
|
||||||
|
```text
|
||||||
|
C++ object lifetime: absent -> constructed -> alive -> destroyed
|
||||||
|
Kernel task state: Ready <-> Running <-> Blocked; Suspended; Deleted
|
||||||
|
```
|
||||||
|
|
||||||
|
Every transition has a trigger, guard or event. Values returned by platform
|
||||||
|
APIs are platform states, not invented wrapper states. A wrapper-specific state
|
||||||
|
machine is allowed only when the code really stores and enforces it.
|
||||||
|
|
||||||
|
### A7 RUNTIME
|
||||||
|
|
||||||
|
**Purpose:** connect the model to a reproducible observation.
|
||||||
|
|
||||||
|
A7 is deliberately a hybrid, not pure UML. It combines an object diagram with
|
||||||
|
selected debugger facts:
|
||||||
|
|
||||||
|
- object and subobject addresses;
|
||||||
|
- `this`, callback context and handles;
|
||||||
|
- fields, ownership pointers, TCB and stack interval;
|
||||||
|
- PC, SP and relevant argument registers;
|
||||||
|
- vptr/vtable target for dynamic dispatch;
|
||||||
|
- symbol/absence-of-symbol proof for static dispatch;
|
||||||
|
- timestamp, target, ELF revision and checkpoint identity.
|
||||||
|
|
||||||
|
Every value is either captured from a deterministic run or marked as a
|
||||||
|
symbolic example. A screenshot without target, source revision and timestamp is
|
||||||
|
illustration, not evidence.
|
||||||
|
|
||||||
|
### A8 PATTERNS
|
||||||
|
|
||||||
|
**Purpose:** explain why the design has its shape and what it costs.
|
||||||
|
|
||||||
|
Each claimed pattern records:
|
||||||
|
|
||||||
|
- intent and problem;
|
||||||
|
- forces/constraints;
|
||||||
|
- canonical participant roles;
|
||||||
|
- exact mapping from role to project class/function;
|
||||||
|
- collaboration that makes the pattern real;
|
||||||
|
- invariant or test;
|
||||||
|
- benefit and cost;
|
||||||
|
- rejected alternative;
|
||||||
|
- conditions in which the pattern should not be used.
|
||||||
|
|
||||||
|
Pattern names are not decorative stereotypes. `Facade` is valid only when a
|
||||||
|
simpler boundary to a subsystem can be identified. `RAII` is valid only when
|
||||||
|
acquisition and release are safely paired for every legal lifetime. A callback
|
||||||
|
bridge may be an Adapter without making the whole wrapper an Adapter.
|
||||||
|
|
||||||
|
## Authored viewpoint record
|
||||||
|
|
||||||
|
The future machine-readable form should preserve this conceptual record. This
|
||||||
|
is a design target, not yet a required `card_source.json` schema extension:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "a3-dispatch",
|
||||||
|
"number": "A3",
|
||||||
|
"label": "DISPATCH",
|
||||||
|
"subtitle": "Dynamic polymorphism through vtable",
|
||||||
|
"purpose": "Trace the C callback to the concrete C++ override.",
|
||||||
|
"stakeholders": ["student", "teacher", "embedded developer"],
|
||||||
|
"concerns": ["C-to-C++ bridge", "void* context", "vtable", "ABI"],
|
||||||
|
"model_kind": "uml-sequence",
|
||||||
|
"source": "doc/assets/a3-dispatch.puml",
|
||||||
|
"fallback_text": "doc/assets/a3-dispatch.txt",
|
||||||
|
"spread_group": "task-dispatch",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "dispatch-vptr",
|
||||||
|
"number": 4,
|
||||||
|
"mode": "RUN",
|
||||||
|
"svg_target": "dispatch-vptr",
|
||||||
|
"code_ref": "include/FreeRTOS/Task.hpp:110",
|
||||||
|
"snapshot_ref": "task.dispatch.vptr",
|
||||||
|
"evidence": "a0, object address, vptr, vtable entry and jalr target"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Required semantics:
|
||||||
|
|
||||||
|
- `id`, `number` and `label` are stable teaching identity;
|
||||||
|
- `subtitle` names the actual mechanism and may differ between cards;
|
||||||
|
- `source` is the text source of the rendered asset;
|
||||||
|
- `fallback_text` is a compact readable map, not generated alt text;
|
||||||
|
- `code_ref` is repository-relative and validated;
|
||||||
|
- CODE steps may have no snapshot and never reset the program;
|
||||||
|
- RUN steps require a deterministic replay target;
|
||||||
|
- `evidence` says what proves the claim, not merely what should be visible;
|
||||||
|
- `spread_group` joins logical continuations without changing A4 print pages.
|
||||||
|
|
||||||
|
## Plain technical rendering profile
|
||||||
|
|
||||||
|
"Plain text UML" means two related things in this project:
|
||||||
|
|
||||||
|
1. diagrams are authored as reviewable text;
|
||||||
|
2. their rendered form resembles a restrained technical drawing rather than a
|
||||||
|
presentation slide.
|
||||||
|
|
||||||
|
The default profile therefore uses:
|
||||||
|
|
||||||
|
- white background, black/charcoal strokes and no shadows;
|
||||||
|
- square or nearly square corners;
|
||||||
|
- one monospaced family for identifiers, fields, methods and evidence;
|
||||||
|
- one sans-serif family only for explanatory prose;
|
||||||
|
- orthogonal relations where the renderer can keep labels legible;
|
||||||
|
- left-aligned members and short, explicit verbs on relations;
|
||||||
|
- no gradients, icons, ornamental colors or oversized titles;
|
||||||
|
- line weight and spacing, not color, as the primary print hierarchy;
|
||||||
|
- visible step anchors such as `01`–`08` that remain useful in SVG and print;
|
||||||
|
- two muted selectable tones in HTML and one subtle active outline;
|
||||||
|
- non-selectable content in neutral gray, never confused with a selectable
|
||||||
|
inactive element.
|
||||||
|
|
||||||
|
PlantUML can generate ASCII/Unicode text output for sequence diagrams. We may
|
||||||
|
ship that output as the fallback for sequences. Other diagram kinds receive a
|
||||||
|
small hand-maintained text map because PlantUML's ASCII renderer does not cover
|
||||||
|
all UML kinds. The fallback communicates topology and reading order; SVG and
|
||||||
|
TeX remain the visual outputs.
|
||||||
|
|
||||||
|
## Orientation, type scale and page visibility
|
||||||
|
|
||||||
|
The default reading direction is vertical. A horizontal layout is reserved for
|
||||||
|
a genuine sequence or timeline whose primary fact is execution order over
|
||||||
|
time. A binding chain is vertical unless time or participant lifelines are
|
||||||
|
being modelled explicitly.
|
||||||
|
|
||||||
|
| Viewpoint | Default orientation | Reason |
|
||||||
|
|---|---|---|
|
||||||
|
| A1 CONTEXT | vertical | responsibility descends from application to platform |
|
||||||
|
| A2 STRUCTURE | vertical | base types, specializations and dependencies form tiers |
|
||||||
|
| A3 DISPATCH | vertical | binding stages form a top-to-bottom chain; use horizontal only for a genuine sequence |
|
||||||
|
| A4 APPLICATION | vertical | application objects lead to observers and invariants |
|
||||||
|
| A5 FLOW | horizontal | messages and time advance in reading order |
|
||||||
|
| A6 STATE | vertical | lifecycle transitions form explicit top-to-bottom paths |
|
||||||
|
| A7 RUNTIME | geometry-dependent | use vertical for an ownership descent, horizontal for a memory/CPU evidence pipeline |
|
||||||
|
| A8 PATTERNS | geometry-dependent | use vertical for tiers and horizontal for a short chain of roles and consequences |
|
||||||
|
|
||||||
|
Use `top to bottom direction` unless the diagram is a genuine sequence or
|
||||||
|
timeline (normally A5). A diagram must not be made horizontal merely to fill
|
||||||
|
page width.
|
||||||
|
|
||||||
|
Physical page orientation follows the rendered model rather than the viewpoint
|
||||||
|
name: use one portrait page, one landscape page, or an explicit semantic page
|
||||||
|
composition. Prefer a single page after a deliberate layout change; introduce
|
||||||
|
tiles only when readable labels and complete semantic blocks still cannot fit.
|
||||||
|
|
||||||
|
The default PlantUML type scale is `14` for ordinary labels, `15` for class
|
||||||
|
names and `13` for class attributes. A smaller value is allowed only when the
|
||||||
|
diagram remains readable at the normal A4 viewer scale. Prefer fewer words,
|
||||||
|
shorter relation labels and a second logical page over shrinking text.
|
||||||
|
|
||||||
|
All diagrams belonging to one card use one shared PlantUML style include. The
|
||||||
|
style is print-first and keeps the following invariants:
|
||||||
|
|
||||||
|
- one monospaced family for nodes, relations and sequence messages;
|
||||||
|
- a `190 px` minimum class width, while content may make a node wider;
|
||||||
|
- `1.5 px` node borders and `1.3 px` arrows;
|
||||||
|
- a near-white node fill, blue-grey borders and dark text with sufficient
|
||||||
|
monochrome-print contrast;
|
||||||
|
- the same base styling for class, state and sequence diagrams;
|
||||||
|
- no shadows, decorative gradients or per-diagram colour inventions.
|
||||||
|
|
||||||
|
Interactive selection is an overlay owned by the viewer. The source diagram
|
||||||
|
must remain fully legible with no selection, while selectable nodes receive a
|
||||||
|
clearer idle treatment than non-interactive context nodes. Selection changes
|
||||||
|
emphasis, not geometry: it must not resize a node, move a label or alter the
|
||||||
|
diagram layout.
|
||||||
|
|
||||||
|
At `content scale = 100%`, the HTML viewer preserves the intrinsic PlantUML
|
||||||
|
scale. A diagram that exceeds the declared A4-safe viewport is split into
|
||||||
|
semantic tiles; it is not silently compressed to `width: 100%`. The joined
|
||||||
|
HTML spread reconstructs the logical page in a CSS grid, while ordinary view
|
||||||
|
and print retain separate physical A4 sheets. Canvas zoom remains an explicit
|
||||||
|
viewer action and may be used to inspect the joined overview.
|
||||||
|
|
||||||
|
Every diagram starts on a new A4 page in HTML and TeX. Its SVG must fit within
|
||||||
|
the page-safe margins with all labels, arrowheads and numbered anchors visible.
|
||||||
|
Long diagrams continue on a new page with stable global step numbers and may be
|
||||||
|
joined by the HTML spread view. Every physical tile remains legible on its own,
|
||||||
|
but its `part`, row and column identify its exact position in the logical model.
|
||||||
|
|
||||||
|
## Large diagrams and page continuity
|
||||||
|
|
||||||
|
A viewpoint is one logical model even when it needs several A4 pages.
|
||||||
|
|
||||||
|
- `page_grid` declares columns, rows and explicit semantic `cuts_x`/`cuts_y`;
|
||||||
|
- a cut may cross whitespace or a connector, but never a class, object, state,
|
||||||
|
participant, note, numbered anchor or other closed semantic block;
|
||||||
|
- the generator validates the rendered SVG geometry and rejects a colliding
|
||||||
|
seam instead of shrinking the diagram or accepting a broken print;
|
||||||
|
- every interactive step belongs to exactly one tile through `step_tiles`;
|
||||||
|
- all tiles use one logical figure number and one global navigation cursor;
|
||||||
|
- each page carries `A<n>`, the same viewpoint title, `part x/y`, and a shared
|
||||||
|
`spread_group`;
|
||||||
|
- duplicated participants at a page boundary are marked `continued`, not
|
||||||
|
treated as new objects;
|
||||||
|
- step numbers remain global across parts;
|
||||||
|
- connectors leaving a page use a named continuation token, for example
|
||||||
|
`D3 -> A5.2`;
|
||||||
|
- HTML can join the parts side by side; print keeps independent A4 pages;
|
||||||
|
- the joined HTML view fits the complete logical row into the viewport, keeps
|
||||||
|
page tops aligned and centers the semantic join rather than either sheet;
|
||||||
|
- a screen-only continuation cue in every part header indicates available
|
||||||
|
neighbours to the left, right, above and below;
|
||||||
|
- selecting a step scrolls to the correct part and preserves one cursor;
|
||||||
|
- no information is encoded only in the physical proximity of two pages.
|
||||||
|
|
||||||
|
The source diagram may use a modest, explicit PlantUML `scale` when that is
|
||||||
|
necessary to place a semantic seam inside the A4-safe viewport. This is a
|
||||||
|
design decision, not automatic fit-to-width. The resulting normal print must
|
||||||
|
still meet the shared type-scale readability gate; HTML hover/canvas zoom is a
|
||||||
|
convenience and never a substitute for readable print.
|
||||||
|
|
||||||
|
## Interaction and correspondence rules
|
||||||
|
|
||||||
|
The diagram, source tree and debugger share one navigation identity:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SERIES -> CARD -> TASK -> BLOCK -> VIEWPOINT/PHASE -> STEP -> SNAPSHOT
|
||||||
|
```
|
||||||
|
|
||||||
|
For every selectable anchor:
|
||||||
|
|
||||||
|
- its visible step number occurs exactly once inside that diagram;
|
||||||
|
- click and keyboard navigation select the same step;
|
||||||
|
- CODE opens the exact code or binary proof and does not mutate runtime;
|
||||||
|
- RUN opens code and may replay only after explicit activation;
|
||||||
|
- compile-time relations use code, `nm`, `readelf` or disassembly evidence;
|
||||||
|
- runtime messages use checkpoint evidence;
|
||||||
|
- focus is a subtle overlay; unselected selectable targets stay visible;
|
||||||
|
- a missing `snapshot_ref` is valid for CODE and invalid for RUN;
|
||||||
|
- the authored SVG target is validated after PlantUML rendering.
|
||||||
|
|
||||||
|
## Diagram-first teaching contract
|
||||||
|
|
||||||
|
The diagram is the primary entry point into an example. A printed card must
|
||||||
|
teach the algorithm, responsibility boundary, state transition or dispatch
|
||||||
|
mechanism before it exposes implementation syntax. Complete source listings
|
||||||
|
do not belong on the card. The card carries the repository URL/QR code, while
|
||||||
|
an interactive CODE anchor opens only the relevant symbol or source range in
|
||||||
|
Neovim.
|
||||||
|
|
||||||
|
The intended learning path is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
goal and scope
|
||||||
|
-> relevant A1-A8 viewpoint
|
||||||
|
-> numbered diagram element
|
||||||
|
-> CODE source anchor or RUN checkpoint
|
||||||
|
-> Neovim / Termdebug / Hazard3 evidence
|
||||||
|
-> timestamped snapshot
|
||||||
|
```
|
||||||
|
|
||||||
|
Consequently:
|
||||||
|
|
||||||
|
- a card uses only the A1-A8 viewpoints which materially explain the subject;
|
||||||
|
- the diagram explains the mechanism, source confirms its implementation and
|
||||||
|
the debugger supplies evidence;
|
||||||
|
- CODE navigation does not restart or mutate the program;
|
||||||
|
- RUN navigation may replay only to a declared deterministic checkpoint;
|
||||||
|
- source excerpts may appear temporarily in the interactive Neovim view, but
|
||||||
|
are not duplicated as full static listings in HTML, TeX or print;
|
||||||
|
- every source jump is attached to a concrete diagram element, relation,
|
||||||
|
message or state rather than to an unrelated code catalogue;
|
||||||
|
- the repository link remains the canonical route to the complete source.
|
||||||
|
|
||||||
|
This order is deliberate: students first reason about algorithms, states and
|
||||||
|
responsibilities, then inspect the C or C++ syntax that implements them.
|
||||||
|
|
||||||
|
## Editable UML layout and JSON persistence
|
||||||
|
|
||||||
|
Interactive UML views may expose an explicit `EDIT` mode. It applies only to
|
||||||
|
UML/model assets; it must not turn the whole card page into a free-form editor
|
||||||
|
or interfere with normal CODE/RUN navigation.
|
||||||
|
|
||||||
|
PlantUML remains the authoritative semantic source for participants,
|
||||||
|
relationships and diagram kind. Manual visual decisions are stored in a
|
||||||
|
sidecar JSON document named after the source, for example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
doc/assets/a2-structure.puml
|
||||||
|
doc/assets/a2-structure.layout.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Saving a layout persists the complete authored presentation state, not only a
|
||||||
|
temporary browser transform. At minimum the sidecar records:
|
||||||
|
|
||||||
|
- diagram identity, source revision and layout schema version;
|
||||||
|
- canvas size, page orientation, scale and optional page-grid metadata;
|
||||||
|
- stable element identity (`id`/`svg_target`), position and stacking order;
|
||||||
|
- width, height, local scale and rotation when the model kind allows it;
|
||||||
|
- displayed name, stereotype, short description and member text overrides;
|
||||||
|
- font family, font size, weight, style, line height and text alignment;
|
||||||
|
- fill, stroke, border width, corner treatment and semantic style role;
|
||||||
|
- group membership, collapsed state and explicit grouping/ungrouping;
|
||||||
|
- relation endpoints, labels and manually placed routing points;
|
||||||
|
- numbered CODE/RUN anchor placement without changing its teaching identity;
|
||||||
|
- editor metadata such as update timestamp and optional author.
|
||||||
|
|
||||||
|
A representative record is:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schema": "stem.uml-layout/v1",
|
||||||
|
"diagram_id": "task01.a2-structure",
|
||||||
|
"source": "a2-structure.puml",
|
||||||
|
"source_digest": "sha256:...",
|
||||||
|
"canvas": {
|
||||||
|
"width": 1120,
|
||||||
|
"height": 760,
|
||||||
|
"orientation": "landscape",
|
||||||
|
"scale": 1
|
||||||
|
},
|
||||||
|
"elements": {
|
||||||
|
"task-base": {
|
||||||
|
"x": 310,
|
||||||
|
"y": 72,
|
||||||
|
"width": 360,
|
||||||
|
"height": 290,
|
||||||
|
"z": 2,
|
||||||
|
"text": {
|
||||||
|
"title": "Task<Derived>",
|
||||||
|
"description": "Static task wrapper",
|
||||||
|
"font_family": "IBM Plex Mono",
|
||||||
|
"font_size": 14,
|
||||||
|
"font_weight": 500,
|
||||||
|
"align": "left"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"role": "wrapper",
|
||||||
|
"fill": "#eef5f8",
|
||||||
|
"stroke": "#397c99",
|
||||||
|
"stroke_width": 1.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"task-to-api": {
|
||||||
|
"from": "task-base",
|
||||||
|
"to": "freertos-api",
|
||||||
|
"label": "calls",
|
||||||
|
"points": [[490, 362], [490, 430], [690, 430]]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updated_at": "2026-07-19T12:00:00+02:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Editor requirements:
|
||||||
|
|
||||||
|
- dragging, resizing, text editing, grouping and relation routing update an
|
||||||
|
in-memory draft first;
|
||||||
|
- `Save layout` validates stable IDs and writes the sidecar atomically;
|
||||||
|
- `Cancel` restores the last saved sidecar without changing PlantUML;
|
||||||
|
- `Reset layout` removes visual overrides and returns to the generated
|
||||||
|
PlantUML geometry only after explicit confirmation;
|
||||||
|
- rerendering PlantUML reapplies compatible overrides by stable ID and reports
|
||||||
|
orphaned overrides instead of silently discarding them;
|
||||||
|
- a source digest mismatch is visible to the editor but does not destroy the
|
||||||
|
saved layout;
|
||||||
|
- normal view and print consume the same saved JSON, so the authored geometry
|
||||||
|
is reproducible outside the editing session;
|
||||||
|
- CODE/RUN step IDs, source references and snapshot identities cannot be
|
||||||
|
renamed accidentally by a visual edit.
|
||||||
|
|
||||||
|
Exact manual coordinates cannot be represented reliably through PlantUML
|
||||||
|
layout hints alone. The sidecar is therefore the canonical manual presentation
|
||||||
|
layer, while `.puml` remains the canonical semantic layer.
|
||||||
|
|
||||||
|
## Two valid A3 profiles for FreeRTOS wrappers
|
||||||
|
|
||||||
|
The viewpoint set intentionally supports both of these designs.
|
||||||
|
|
||||||
|
### Current teaching wrapper in K02
|
||||||
|
|
||||||
|
```text
|
||||||
|
A2 STRUCTURE Task<Derived>, SumTask, SupervisorTask
|
||||||
|
A3 DISPATCH CRTP static dispatch
|
||||||
|
void* -> Task<Derived>* -> Derived* -> run()
|
||||||
|
A7 RUNTIME same object address; direct/inlined target; no vtable symbol
|
||||||
|
```
|
||||||
|
|
||||||
|
This matches the current K02 acceptance gate: no virtual dispatch, vtable,
|
||||||
|
RTTI or hosted runtime.
|
||||||
|
|
||||||
|
### `jonenz/FreeRTOS-Cpp` reference design
|
||||||
|
|
||||||
|
```text
|
||||||
|
A2 STRUCTURE TaskBase <- Task / StaticTask<N> <- application task
|
||||||
|
A3 DISPATCH dynamic dispatch
|
||||||
|
void* -> TaskBase* -> taskEntry() -> virtual taskFunction()
|
||||||
|
A7 RUNTIME object address, vptr, vtable entry and indirect target
|
||||||
|
```
|
||||||
|
|
||||||
|
The upstream `Task.hpp` creates dynamic/static kernel tasks in constructors,
|
||||||
|
passes `this` to `callTaskFunction(void*)`, and uses a virtual
|
||||||
|
`taskFunction()`. Its `TaskBase` destructor may call `vTaskDelete(handle)` when
|
||||||
|
configured. These are reference facts, not automatic design choices for our
|
||||||
|
teaching wrapper. Adopting that design would require changing K02's lifecycle,
|
||||||
|
ABI and no-vtable acceptance contracts explicitly.
|
||||||
|
|
||||||
|
Reference:
|
||||||
|
<https://github.com/jonenz/FreeRTOS-Cpp/blob/develop/FreeRTOS-Cpp/include/FreeRTOS/Task.hpp>
|
||||||
|
|
||||||
|
## Target profile: Boost.Asio and standard threads
|
||||||
|
|
||||||
|
This is the required future compatibility profile. It does not rename the
|
||||||
|
fixed tabs. Mechanism-specific wording belongs in subtitles:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A1 CONTEXT · application, Asio, io_context, worker threads, OS
|
||||||
|
A2 STRUCTURE · Server, Session, socket, strand and ThreadPool
|
||||||
|
A3 DISPATCH · executor and completion-handler dispatch
|
||||||
|
A4 APPLICATION · one concrete Session or Client
|
||||||
|
A5 FLOW · asynchronous initiation and completion
|
||||||
|
A6 STATE · operation, session and thread lifecycles
|
||||||
|
A7 RUNTIME · threads, executors, handlers and owned buffers
|
||||||
|
A8 PATTERNS · Proactor and verified collaboration roles
|
||||||
|
```
|
||||||
|
|
||||||
|
The fixed label remains `A5 FLOW`, not `A5 ASYNC FLOW`; the UI subtitle may be
|
||||||
|
`Async completion flow`. Likewise the fixed label remains `A7 RUNTIME`, with
|
||||||
|
`Threads / executors` as a subtitle. This keeps cards comparable while still
|
||||||
|
naming the actual mechanism.
|
||||||
|
|
||||||
|
### A1–A8 mapping
|
||||||
|
|
||||||
|
**A1 CONTEXT** shows this dependency/execution stack:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Application Session / Server
|
||||||
|
│ initiates
|
||||||
|
▼
|
||||||
|
Boost.Asio async operations, executors and strands
|
||||||
|
│ queue completions in
|
||||||
|
▼
|
||||||
|
io_context
|
||||||
|
│ run() is called by
|
||||||
|
▼
|
||||||
|
std::thread / std::jthread workers
|
||||||
|
│ use
|
||||||
|
▼
|
||||||
|
operating-system sockets and scheduling
|
||||||
|
```
|
||||||
|
|
||||||
|
**A2 STRUCTURE** shows the static ownership and dependency graph: `Server`,
|
||||||
|
`Session`, `tcp::socket`, executor/strand, buffers, `io_context` and the worker
|
||||||
|
collection. It must make lifetime ownership explicit, especially when a
|
||||||
|
session uses `shared_from_this()` to outlive an initiating call.
|
||||||
|
|
||||||
|
**A3 DISPATCH** traces an initiating function through the outstanding
|
||||||
|
operation, associated executor, completion queue and selected handler. It must
|
||||||
|
not draw `async_read` as if it created a user thread. Completion handlers are
|
||||||
|
invoked only by threads currently running `io_context::run()`; several such
|
||||||
|
threads form a pool from which `io_context` may choose. A strand adds strictly
|
||||||
|
sequential handler invocation, not physical affinity to one thread.
|
||||||
|
|
||||||
|
**A4 APPLICATION** collapses Asio infrastructure and shows one concrete
|
||||||
|
protocol/session contract, for example `EchoSession::start/read/onRead/write`.
|
||||||
|
|
||||||
|
**A5 FLOW** uses the explicit sequence:
|
||||||
|
|
||||||
|
```text
|
||||||
|
initiate -> outstanding -> operation completes -> handler queued
|
||||||
|
-> one run() thread invokes handler -> next operation or completion
|
||||||
|
```
|
||||||
|
|
||||||
|
**A6 STATE** separates at least three domains when present:
|
||||||
|
|
||||||
|
```text
|
||||||
|
jthread object: absent -> joinable -> stop requested -> joined
|
||||||
|
async operation: initiated -> outstanding -> queued -> running -> done
|
||||||
|
application Session: Created -> Reading -> Processing -> Writing -> Closed
|
||||||
|
```
|
||||||
|
|
||||||
|
The `std::jthread` destructor requests stop and joins when the object is still
|
||||||
|
joinable. A session state is application data; it must not be inferred merely
|
||||||
|
from a handler being present in a queue.
|
||||||
|
|
||||||
|
**A7 RUNTIME** records `io_context`, worker IDs, current handler and thread,
|
||||||
|
executor/strand identity, socket descriptor, operation kind, session address,
|
||||||
|
buffer lifetime and relevant smart-pointer ownership. The evidence view uses
|
||||||
|
`info threads`, backtraces and object inspection rather than RTOS TCB fields.
|
||||||
|
|
||||||
|
**A8 PATTERNS** may identify Proactor, callback/continuation, serialized
|
||||||
|
execution through a strand, thread pool, RAII thread ownership and lifetime
|
||||||
|
management. These are classified carefully: `std::jthread` ownership is a C++
|
||||||
|
idiom, a thread pool is a concurrency architecture, and not every completion
|
||||||
|
handler constitutes a GoF Command. Boost.Asio documents its asynchronous model
|
||||||
|
as Proactor; the card must still map each claimed role to project code.
|
||||||
|
|
||||||
|
The central invariant for every future Asio card is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
asynchronous operation != thread
|
||||||
|
|
||||||
|
operation completion -> associated executor -> io_context
|
||||||
|
-> one thread currently executing run()
|
||||||
|
```
|
||||||
|
|
||||||
|
Primary references for this profile:
|
||||||
|
|
||||||
|
- <https://www.boost.org/doc/libs/latest/doc/html/boost_asio/overview/core/threads.html>
|
||||||
|
- <https://www.boost.org/doc/libs/latest/doc/html/boost_asio/overview/core/strands.html>
|
||||||
|
- <https://www.boost.org/doc/libs/latest/doc/html/boost_asio/overview/core/async.html>
|
||||||
|
- <https://eel.is/c++draft/thread.thread.class>
|
||||||
|
- <https://eel.is/c++draft/thread.jthread.class>
|
||||||
|
|
||||||
|
## Definition of done for a card view
|
||||||
|
|
||||||
|
A viewpoint is ready when:
|
||||||
|
|
||||||
|
1. its purpose and question are stated;
|
||||||
|
2. the chosen UML/model kind fits that question;
|
||||||
|
3. every relation has one unambiguous meaning;
|
||||||
|
4. every selectable item has a stable anchor and code reference;
|
||||||
|
5. runtime claims have reproducible evidence;
|
||||||
|
6. compile-time claims use compiler/binary evidence rather than fake runtime;
|
||||||
|
7. the text fallback remains understandable without SVG;
|
||||||
|
8. the view prints in monochrome and works as an interactive HTML asset;
|
||||||
|
9. continuation across pages preserves identity and step order;
|
||||||
|
10. A8 names costs and rejected alternatives, not only benefits.
|
||||||
+2445
-113
File diff suppressed because it is too large
Load Diff
+1449
-165
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,23 @@
|
|||||||
"debug_checkpoints": {
|
"debug_checkpoints": {
|
||||||
"$ref": "#/$defs/debugCheckpoints"
|
"$ref": "#/$defs/debugCheckpoints"
|
||||||
},
|
},
|
||||||
|
"viewpoints": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 8,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/viewpoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"debug_strategies": {
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
|
||||||
|
},
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/$defs/debugStrategy"
|
||||||
|
}
|
||||||
|
},
|
||||||
"render_dictionary": {
|
"render_dictionary": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Czy React/HTML ma dodać końcową stronę słownika WE/EN/EK/KW."
|
"description": "Czy React/HTML ma dodać końcową stronę słownika WE/EN/EK/KW."
|
||||||
@@ -309,6 +326,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||||
},
|
},
|
||||||
|
"url_domain": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[A-Za-z0-9.-]+$",
|
||||||
|
"description": "Opcjonalna domena dopinana do subdomeny UUID, np. mpabi.pl."
|
||||||
|
},
|
||||||
"url_port": { "type": ["string", "integer"] },
|
"url_port": { "type": ["string", "integer"] },
|
||||||
"doc_uuid": {
|
"doc_uuid": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -605,6 +627,27 @@
|
|||||||
"enum": ["flow", "one-per-page"],
|
"enum": ["flow", "one-per-page"],
|
||||||
"description": "Sposób paginacji assetów. one-per-page wymusza osobną kartkę A4 w HTML i PDF."
|
"description": "Sposób paginacji assetów. one-per-page wymusza osobną kartkę A4 w HTML i PDF."
|
||||||
},
|
},
|
||||||
|
"page_orientation": {
|
||||||
|
"enum": ["portrait", "landscape"],
|
||||||
|
"default": "portrait",
|
||||||
|
"description": "Orientacja fizycznych stron tej sekcji w HTML i TeX. Dobierana do geometrii diagramu, niezależnie od page_grid."
|
||||||
|
},
|
||||||
|
"spread_group": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$",
|
||||||
|
"description": "Łączy kolejne strony assetów w opcjonalny ekranowy spread; druk nadal zachowuje osobne kartki A4."
|
||||||
|
},
|
||||||
|
"spread_label": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "Krótka nazwa logicznego diagramu pokazywana przy przełączniku spreadu."
|
||||||
|
},
|
||||||
|
"spread_columns": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 12,
|
||||||
|
"description": "Liczba fizycznych stron A4 w jednym wierszu logicznego spreadu."
|
||||||
|
},
|
||||||
"area_tree_refs": {
|
"area_tree_refs": {
|
||||||
"$ref": "#/$defs/stringList"
|
"$ref": "#/$defs/stringList"
|
||||||
},
|
},
|
||||||
@@ -674,6 +717,72 @@
|
|||||||
},
|
},
|
||||||
"interactive": {
|
"interactive": {
|
||||||
"$ref": "#/$defs/interactiveAsset"
|
"$ref": "#/$defs/interactiveAsset"
|
||||||
|
},
|
||||||
|
"page_grid": {
|
||||||
|
"$ref": "#/$defs/diagramPageGrid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"diagramPageGrid": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["columns", "rows"],
|
||||||
|
"properties": {
|
||||||
|
"columns": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 12
|
||||||
|
},
|
||||||
|
"rows": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 12
|
||||||
|
},
|
||||||
|
"page_width": {
|
||||||
|
"type": "number",
|
||||||
|
"exclusiveMinimum": 0,
|
||||||
|
"default": 660,
|
||||||
|
"description": "Referencyjna szerokość bezpiecznego pola diagramu A4 w jednostkach PlantUML."
|
||||||
|
},
|
||||||
|
"page_height": {
|
||||||
|
"type": "number",
|
||||||
|
"exclusiveMinimum": 0,
|
||||||
|
"default": 760,
|
||||||
|
"description": "Referencyjna wysokość bezpiecznego pola diagramu A4 w jednostkach PlantUML."
|
||||||
|
},
|
||||||
|
"step_tiles": {
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
|
||||||
|
},
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
|
"description": "Jawne przypisanie id kroku do numeru kafla w porządku wierszowym, liczonego od 1."
|
||||||
|
},
|
||||||
|
"cuts_x": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"exclusiveMinimum": 0
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"description": "Semantyczne pionowe linie cięcia w układzie współrzędnych SVG; nie mogą przecinać bloków ani etykiet."
|
||||||
|
},
|
||||||
|
"cuts_y": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"exclusiveMinimum": 0
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"description": "Semantyczne poziome linie cięcia w układzie współrzędnych SVG; nie mogą przecinać bloków ani etykiet."
|
||||||
|
},
|
||||||
|
"overview": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "W HTML udostępnia pomniejszony podgląd całej logicznej planszy obok widoku kafli 1:1."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@@ -883,6 +992,11 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["stop"],
|
"required": ["stop"],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"event_id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^E[0-9]{2}$",
|
||||||
|
"description": "Semantic event shared by comparable cards; the snapshot id remains card-local."
|
||||||
|
},
|
||||||
"stop": {
|
"stop": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["symbol", "offset"],
|
"required": ["symbol", "offset"],
|
||||||
@@ -962,12 +1076,30 @@
|
|||||||
},
|
},
|
||||||
"umlSequenceStep": {
|
"umlSequenceStep": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["id", "number", "label", "description", "snapshot"],
|
"required": ["id", "number", "label", "description"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": { "type": "string", "minLength": 1 },
|
"id": { "type": "string", "minLength": 1 },
|
||||||
"number": { "type": "integer", "minimum": 1 },
|
"number": { "type": "integer", "minimum": 1 },
|
||||||
"label": { "type": "string", "minLength": 1 },
|
"label": { "type": "string", "minLength": 1 },
|
||||||
|
"mode": {
|
||||||
|
"enum": ["CODE", "RUN"],
|
||||||
|
"description": "CODE opens source only; RUN opens source and may replay snapshot_ref. When omitted, steps with snapshot_ref infer RUN and code-only steps infer CODE."
|
||||||
|
},
|
||||||
|
"event_id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^E[0-9]{2}$"
|
||||||
|
},
|
||||||
|
"strategy_ref": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "Reference to a structured entry in debug_strategies."
|
||||||
|
},
|
||||||
"description": { "type": "string", "minLength": 1 },
|
"description": { "type": "string", "minLength": 1 },
|
||||||
|
"evidence": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "Expected source, ELF, disassembly, or runtime proof for the teaching step."
|
||||||
|
},
|
||||||
"code_ref": { "type": "string" },
|
"code_ref": { "type": "string" },
|
||||||
"progress_id": { "type": "string", "minLength": 1 },
|
"progress_id": { "type": "string", "minLength": 1 },
|
||||||
"snapshot_ref": {
|
"snapshot_ref": {
|
||||||
@@ -981,8 +1113,67 @@
|
|||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"description": "Dokładny numer lub etykieta wiadomości PlantUML wybieranej w inline SVG."
|
"description": "Dokładny numer lub etykieta wiadomości PlantUML wybieranej w inline SVG."
|
||||||
},
|
},
|
||||||
|
"svg_target": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "Stable PlantUML SVG element id. If absent, the renderer focuses the svg_label row."
|
||||||
|
},
|
||||||
"snapshot": { "$ref": "#/$defs/umlSnapshot" }
|
"snapshot": { "$ref": "#/$defs/umlSnapshot" }
|
||||||
},
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": { "mode": { "const": "CODE" } },
|
||||||
|
"required": ["mode"]
|
||||||
|
},
|
||||||
|
"then": { "not": { "required": ["snapshot_ref"] } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"viewpoint": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "label", "status"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"enum": ["A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8"]
|
||||||
|
},
|
||||||
|
"label": { "type": "string", "minLength": 1 },
|
||||||
|
"subtitle": { "type": "string" },
|
||||||
|
"status": { "enum": ["enabled", "unavailable"] },
|
||||||
|
"target_label": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[A-Za-z0-9][A-Za-z0-9:.-]*$"
|
||||||
|
},
|
||||||
|
"reason": { "type": "string", "minLength": 1 }
|
||||||
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": { "properties": { "status": { "const": "enabled" } }, "required": ["status"] },
|
||||||
|
"then": { "required": ["target_label"] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": { "properties": { "status": { "const": "unavailable" } }, "required": ["status"] },
|
||||||
|
"then": { "required": ["reason"] }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"debugStrategy": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "kind", "action", "expected_observations", "assertions", "evidence_fields"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string", "minLength": 1 },
|
||||||
|
"kind": { "enum": ["code", "run"] },
|
||||||
|
"prerequisites": { "$ref": "#/$defs/stringList" },
|
||||||
|
"code_ref": { "type": "string", "minLength": 1 },
|
||||||
|
"action": { "enum": ["open", "replay"] },
|
||||||
|
"layout": { "$ref": "#/$defs/stringList" },
|
||||||
|
"commands": { "$ref": "#/$defs/stringList" },
|
||||||
|
"expected_observations": { "$ref": "#/$defs/stringList" },
|
||||||
|
"assertions": { "$ref": "#/$defs/stringList" },
|
||||||
|
"evidence_fields": { "$ref": "#/$defs/stringList" }
|
||||||
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"umlSequencePhase": {
|
"umlSequencePhase": {
|
||||||
@@ -1006,7 +1197,7 @@
|
|||||||
"required": ["kind"],
|
"required": ["kind"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"kind": {
|
"kind": {
|
||||||
"enum": ["allocator-memory-flow", "uml-sequence"]
|
"enum": ["allocator-memory-flow", "uml-sequence", "uml-class"]
|
||||||
},
|
},
|
||||||
"storage_key": {
|
"storage_key": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
Executable
+419
@@ -0,0 +1,419 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Expand a concise FreeRTOS C card spec into card_source.json.
|
||||||
|
|
||||||
|
The series spec remains the authored source for repeated metadata. Program
|
||||||
|
code, debug recipes and PlantUML diagrams stay card-local. The resulting
|
||||||
|
card_source.json is deterministic and is consumed by render_card.py.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import uuid
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
GITEA_BASE_URL = "https://zsl-gitea.mpabi.pl"
|
||||||
|
GITEA_SOURCE_ORG = "edu-freertos-c"
|
||||||
|
PUBLICATION_HOST_UUID = "dce7fb9d-7b2f-5d49-96a2-3a30d3070b84"
|
||||||
|
PUBLICATION_DOMAIN = "mpabi.pl"
|
||||||
|
|
||||||
|
|
||||||
|
def file_sha256(path: Path) -> str:
|
||||||
|
digest = hashlib.sha256()
|
||||||
|
with path.open("rb") as stream:
|
||||||
|
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||||
|
digest.update(chunk)
|
||||||
|
return digest.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def git_blob(path: Path) -> str:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "hash-object", str(path)],
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def stable_uuid(name: str) -> str:
|
||||||
|
return str(uuid.uuid5(uuid.NAMESPACE_URL, name))
|
||||||
|
|
||||||
|
|
||||||
|
def load_json(path: Path) -> dict[str, Any]:
|
||||||
|
with path.open("r", encoding="utf-8") as stream:
|
||||||
|
return json.load(stream)
|
||||||
|
|
||||||
|
|
||||||
|
def strategy(value: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
result = {
|
||||||
|
"id": value["id"],
|
||||||
|
"kind": value["kind"],
|
||||||
|
"action": "open" if value["kind"] == "code" else "replay",
|
||||||
|
"expected_observations": value["expected_observations"],
|
||||||
|
"assertions": value["assertions"],
|
||||||
|
"evidence_fields": value["evidence_fields"],
|
||||||
|
}
|
||||||
|
for key in ("prerequisites", "layout", "commands", "code_ref"):
|
||||||
|
if key in value:
|
||||||
|
result[key] = value[key]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def interactive_asset(
|
||||||
|
card_id: str,
|
||||||
|
task_label: str,
|
||||||
|
section: dict[str, Any],
|
||||||
|
asset: dict[str, Any],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
phases = asset["phases"]
|
||||||
|
step_tiles = {
|
||||||
|
step["id"]: 1
|
||||||
|
for phase in phases
|
||||||
|
for step in phase["steps"]
|
||||||
|
}
|
||||||
|
orientation = section.get("orientation", "portrait")
|
||||||
|
page_width = 940 if orientation == "landscape" else 660
|
||||||
|
page_height = 560 if orientation == "landscape" else 760
|
||||||
|
kind = "uml-sequence" if asset.get("diagram_kind") == "sequence" else "uml-class"
|
||||||
|
stem = asset["stem"]
|
||||||
|
return {
|
||||||
|
"path": f"assets/{stem}.png",
|
||||||
|
"html_path": f"assets/{stem}.svg",
|
||||||
|
"source_path": f"assets/{stem}.puml",
|
||||||
|
"caption": asset["caption"],
|
||||||
|
"label": asset["label"],
|
||||||
|
"alt": asset["alt"],
|
||||||
|
"kind": "diagram",
|
||||||
|
"width": 1.0,
|
||||||
|
"page_grid": {
|
||||||
|
"columns": 1,
|
||||||
|
"rows": 1,
|
||||||
|
"page_width": page_width,
|
||||||
|
"page_height": page_height,
|
||||||
|
"overview": True,
|
||||||
|
"step_tiles": step_tiles,
|
||||||
|
},
|
||||||
|
"interactive": {
|
||||||
|
"kind": kind,
|
||||||
|
"storage_key": f"{card_id.lower()}-{stem}",
|
||||||
|
"title": asset["title"],
|
||||||
|
"task": {"id": "task01", "label": task_label},
|
||||||
|
"block": {
|
||||||
|
"id": section["id"].lower(),
|
||||||
|
"label": f"{section['id']} {section['label']}",
|
||||||
|
"description": section["description"],
|
||||||
|
},
|
||||||
|
"phases": phases,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def section_value(
|
||||||
|
card_id: str,
|
||||||
|
task_label: str,
|
||||||
|
section: dict[str, Any],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
result: dict[str, Any] = {
|
||||||
|
"title": f"{section['id']} — {section['title']}",
|
||||||
|
"order": section["order"],
|
||||||
|
"content_kind": "prose",
|
||||||
|
"asset_page_mode": "one-per-page",
|
||||||
|
"page_orientation": section.get("orientation", "portrait"),
|
||||||
|
"content_tex": section["content_tex"],
|
||||||
|
"assets": [
|
||||||
|
interactive_asset(card_id, task_label, section, asset)
|
||||||
|
for asset in section["assets"]
|
||||||
|
],
|
||||||
|
}
|
||||||
|
for key in ("spread_group", "spread_label", "spread_columns"):
|
||||||
|
if key in section:
|
||||||
|
result[key] = section[key]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def tree_item(
|
||||||
|
effect_ref: str,
|
||||||
|
criterion_ref: str,
|
||||||
|
display: str,
|
||||||
|
tree_id: str,
|
||||||
|
text: str,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"effect_ref": effect_ref,
|
||||||
|
"display": display,
|
||||||
|
"source": "LOCAL",
|
||||||
|
"official": "RTOS",
|
||||||
|
"local": display.rsplit(".", 1)[-1],
|
||||||
|
"kind": "EK" if ".TECH." in tree_id else "EN",
|
||||||
|
"tree_id": tree_id,
|
||||||
|
"text": text,
|
||||||
|
"kw": [{
|
||||||
|
"criterion_ref": criterion_ref,
|
||||||
|
"display": display.replace("EN ", "KW ").replace("EK ", "KW "),
|
||||||
|
"source": "LOCAL",
|
||||||
|
"kind": "KW",
|
||||||
|
"official": "RTOS",
|
||||||
|
"local": display.rsplit(".", 1)[-1],
|
||||||
|
"text": "Łączy kod, diagram i zweryfikowany dowód.",
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build(repo: Path, spec: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
card = spec["card"]
|
||||||
|
number = card["number"]
|
||||||
|
prefix = f"FC{number}"
|
||||||
|
repo_name = repo.name
|
||||||
|
card_id = f"mpabi-freertos-c-{number}-{card['slug']}"
|
||||||
|
card_uuid = stable_uuid(f"https://stem.local/cards/{card_id}")
|
||||||
|
task_uuid = stable_uuid(f"https://stem.local/cards/{card_id}/task01")
|
||||||
|
task_label = f"Task01 · {spec['task']['short_label']}"
|
||||||
|
revision_date = card.get("revision_date", "2026-07-19T00:00:00+02:00")
|
||||||
|
source = repo / spec["artifact"]["source"]
|
||||||
|
elf = repo / spec["artifact"]["elf"]
|
||||||
|
image = repo / spec["artifact"]["image"]
|
||||||
|
|
||||||
|
viewpoints = []
|
||||||
|
targets = {section["id"]: section["assets"][0]["label"] for section in spec["sections"]}
|
||||||
|
for viewpoint in spec["viewpoints"]:
|
||||||
|
item = {
|
||||||
|
"id": viewpoint["id"],
|
||||||
|
"label": viewpoint["label"],
|
||||||
|
"subtitle": viewpoint.get("subtitle", ""),
|
||||||
|
"status": viewpoint["status"],
|
||||||
|
}
|
||||||
|
if viewpoint["status"] == "enabled":
|
||||||
|
item["target_label"] = targets[viewpoint["id"]]
|
||||||
|
else:
|
||||||
|
item["reason"] = viewpoint["reason"]
|
||||||
|
viewpoints.append(item)
|
||||||
|
|
||||||
|
criterion_ref = f"{prefix}.KW01"
|
||||||
|
en_ref = f"{prefix}.EN01"
|
||||||
|
ek_ref = f"{prefix}.EK01"
|
||||||
|
requirement_ref = f"{prefix}.WE01"
|
||||||
|
sections = [
|
||||||
|
section_value(prefix, task_label, section)
|
||||||
|
for section in spec["sections"]
|
||||||
|
]
|
||||||
|
sections.append({
|
||||||
|
"title": f"Task01 — {spec['task']['short_label']}",
|
||||||
|
"order": 90,
|
||||||
|
"content_kind": "tasks",
|
||||||
|
"task_refs": ["task01"],
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"$schema": "../../../tools/card-layouts/schemas/card-source.schema.json",
|
||||||
|
"schema": "esc-card-source.v1",
|
||||||
|
"card": {
|
||||||
|
"id": card_id,
|
||||||
|
"series": "freertos-c",
|
||||||
|
"series_title": "FreeRTOS C",
|
||||||
|
"number": number,
|
||||||
|
"count": "15",
|
||||||
|
"slug": card["slug"],
|
||||||
|
"title": card["title"],
|
||||||
|
"topic": card["topic"],
|
||||||
|
"project": "Freestanding C nad FreeRTOS",
|
||||||
|
"subject": "Informatyka",
|
||||||
|
"level": f"Rok 2 · L{number} · RV32I/Hazard3",
|
||||||
|
"revision_date": revision_date,
|
||||||
|
"status": card.get("status", "W przygotowaniu"),
|
||||||
|
"version": card.get("version", "v00.01"),
|
||||||
|
"uuid": card_uuid,
|
||||||
|
"author": "M. Pabiszczak",
|
||||||
|
"year": "2026",
|
||||||
|
},
|
||||||
|
"generated": {
|
||||||
|
"tex": "doc/generated/main.tex",
|
||||||
|
"html": "web/index.html",
|
||||||
|
"html_css": "web/style.css",
|
||||||
|
"html_tree_inspector": False,
|
||||||
|
"react_app": True,
|
||||||
|
},
|
||||||
|
"render_dictionary": False,
|
||||||
|
"viewpoints": viewpoints,
|
||||||
|
"debug_strategies": {
|
||||||
|
item["id"]: strategy(item)
|
||||||
|
for item in spec["strategies"]
|
||||||
|
},
|
||||||
|
"template": "templates/karta-klasyczna.json",
|
||||||
|
"title_block": {
|
||||||
|
"category": "KARTA PRACY · INFORMATYKA",
|
||||||
|
"prepared_by": "M. Pabiszczak",
|
||||||
|
"prepared_on": revision_date,
|
||||||
|
"title": card["title"],
|
||||||
|
"url": f"https://{PUBLICATION_HOST_UUID}.{PUBLICATION_DOMAIN}/{task_uuid}",
|
||||||
|
"repository_url": f"{GITEA_BASE_URL}/{GITEA_SOURCE_ORG}/{repo_name}",
|
||||||
|
"url_host_uuid": PUBLICATION_HOST_UUID,
|
||||||
|
"url_domain": PUBLICATION_DOMAIN,
|
||||||
|
"doc_uuid": task_uuid,
|
||||||
|
"revision": card.get("version", "v00.01"),
|
||||||
|
"issued_on": revision_date,
|
||||||
|
"series": f"FREERTOS-C-{number}",
|
||||||
|
"document_type": "karta pracy",
|
||||||
|
"tool": "card-layouts",
|
||||||
|
"show_qr": True,
|
||||||
|
"show_repository_qr": True,
|
||||||
|
"height_cm": 2.6,
|
||||||
|
"repeat_on_every_page": True,
|
||||||
|
"replace_front_matter": True,
|
||||||
|
},
|
||||||
|
"front_page_scope": {
|
||||||
|
"title": "Cel karty",
|
||||||
|
"content_tex": spec["front"]["goal"],
|
||||||
|
"scope_title": "Zakres karty",
|
||||||
|
"scope_content_tex": spec["front"]["scope"],
|
||||||
|
"scope_table": {
|
||||||
|
"headers": ["Lekcja", "Task", "Najważniejsza idea", "Priorytet", "Status", "Version"],
|
||||||
|
"rows": [{
|
||||||
|
"chapter": prefix,
|
||||||
|
"task": "Task01",
|
||||||
|
"idea_tex": card["topic"],
|
||||||
|
"priority": "główny",
|
||||||
|
"status": "ready",
|
||||||
|
"version": card.get("version", "v00.01"),
|
||||||
|
"key": True,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"side_margin_tree_layout": {
|
||||||
|
"columns": [
|
||||||
|
{"id": "zawodowe", "label": "TECH", "side": "left", "tree": "WE -> EK -> KW", "description": "Dowody runtime."},
|
||||||
|
{"id": "ogolne", "label": "OG", "side": "right", "tree": "WE -> EN -> KW", "description": "Model FreeRTOS C."},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"learning_effects": {
|
||||||
|
en_ref: {
|
||||||
|
"bloom_level": "Analiza",
|
||||||
|
"label": spec["learning"]["model_label"],
|
||||||
|
"text": spec["learning"]["model"],
|
||||||
|
"assessment_criteria": [criterion_ref],
|
||||||
|
},
|
||||||
|
ek_ref: {
|
||||||
|
"bloom_level": "Zastosowanie",
|
||||||
|
"label": spec["learning"]["replay_label"],
|
||||||
|
"text": spec["learning"]["replay"],
|
||||||
|
"assessment_criteria": [criterion_ref],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"assessment_criteria": {
|
||||||
|
criterion_ref: {
|
||||||
|
"text": spec["learning"]["criterion"],
|
||||||
|
"learning_effects": [en_ref, ek_ref],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"educational_requirements": {
|
||||||
|
requirement_ref: {
|
||||||
|
"text": spec["learning"]["requirement"],
|
||||||
|
"label": card["topic"],
|
||||||
|
"learning_effects": [en_ref, ek_ref],
|
||||||
|
"learning_tree": {
|
||||||
|
"schema": "we-learning-tree.v1",
|
||||||
|
"policy": "Każda aktywna kotwica wskazuje kod lub checkpoint.",
|
||||||
|
"ogolne": [tree_item(
|
||||||
|
en_ref,
|
||||||
|
criterion_ref,
|
||||||
|
f"EN LOCAL RTOS.{number}",
|
||||||
|
f"{requirement_ref}.OG.LOCAL.RTOS.{number}",
|
||||||
|
spec["learning"]["model"],
|
||||||
|
)],
|
||||||
|
"zawodowe": [tree_item(
|
||||||
|
ek_ref,
|
||||||
|
criterion_ref,
|
||||||
|
f"EK LOCAL RTOS.{number}",
|
||||||
|
f"{requirement_ref}.TECH.LOCAL.RTOS.{number}",
|
||||||
|
spec["learning"]["replay"],
|
||||||
|
)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"debug_checkpoints": {
|
||||||
|
"schema": "stem-debug-checkpoints.v1",
|
||||||
|
"semantics": "deterministic-replay",
|
||||||
|
"artifact": {
|
||||||
|
"source": spec["artifact"]["source"],
|
||||||
|
"source_git_blob": git_blob(source),
|
||||||
|
"elf": spec["artifact"]["elf"],
|
||||||
|
"hazard3_elf_sha256": file_sha256(elf),
|
||||||
|
"hazard3_image": spec["artifact"]["image"],
|
||||||
|
"hazard3_image_sha256": file_sha256(image),
|
||||||
|
},
|
||||||
|
"targets": {
|
||||||
|
"hazard3-sim": {
|
||||||
|
"adapter": "hazard3-reset-load-replay.v1",
|
||||||
|
"baseline": "restart-backend",
|
||||||
|
"clean_ram": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"items": spec["checkpoints"],
|
||||||
|
},
|
||||||
|
"sections": sections,
|
||||||
|
"tasks": {
|
||||||
|
"task01": {
|
||||||
|
"title": spec["task"]["title"],
|
||||||
|
"uuid": task_uuid,
|
||||||
|
"prompt_tex": spec["task"]["prompt_tex"],
|
||||||
|
"criterion": spec["learning"]["criterion"],
|
||||||
|
"conclusion_tex": spec["task"]["conclusion_tex"],
|
||||||
|
"flow": spec["task"]["flow"],
|
||||||
|
"educational_requirement_refs": [requirement_ref],
|
||||||
|
"learning_effect_refs": [en_ref, ek_ref],
|
||||||
|
"assessment_criterion_ref": criterion_ref,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tasks_order": ["task01"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def validate_references(card_source: dict[str, Any], repo: Path) -> None:
|
||||||
|
checkpoints = set(card_source["debug_checkpoints"]["items"])
|
||||||
|
strategies = set(card_source["debug_strategies"])
|
||||||
|
for section in card_source["sections"]:
|
||||||
|
for asset in section.get("assets", []):
|
||||||
|
for phase in asset["interactive"]["phases"]:
|
||||||
|
for step in phase["steps"]:
|
||||||
|
if step["strategy_ref"] not in strategies:
|
||||||
|
raise ValueError(f"missing strategy: {step['strategy_ref']}")
|
||||||
|
code_ref = step.get("code_ref")
|
||||||
|
if code_ref:
|
||||||
|
file_name, line_text = code_ref.rsplit(":", 1)
|
||||||
|
file_path = repo / file_name
|
||||||
|
if not file_path.is_file():
|
||||||
|
raise ValueError(f"missing code_ref file: {code_ref}")
|
||||||
|
line = int(line_text)
|
||||||
|
line_count = sum(1 for _ in file_path.open(encoding="utf-8", errors="ignore"))
|
||||||
|
if line < 1 or line > line_count:
|
||||||
|
raise ValueError(f"invalid code_ref line: {code_ref}")
|
||||||
|
if step.get("mode") == "RUN":
|
||||||
|
snapshot = step.get("snapshot_ref")
|
||||||
|
if snapshot not in checkpoints:
|
||||||
|
raise ValueError(f"missing checkpoint: {snapshot}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("repo", type=Path)
|
||||||
|
parser.add_argument("--spec", default="json/card_spec.json")
|
||||||
|
args = parser.parse_args()
|
||||||
|
repo = args.repo.resolve()
|
||||||
|
spec = load_json(repo / args.spec)
|
||||||
|
card_source = build(repo, spec)
|
||||||
|
validate_references(card_source, repo)
|
||||||
|
output = repo / "json/card_source.json"
|
||||||
|
output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with output.open("w", encoding="utf-8") as stream:
|
||||||
|
json.dump(card_source, stream, ensure_ascii=False, indent=2)
|
||||||
|
stream.write("\n")
|
||||||
|
print(output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+881
-34
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+61
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user