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:
@@ -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.
|
||||
Reference in New Issue
Block a user