Files
card-layouts/docs/OFFLINE-FIRST-DEPLOYMENT.md
mpabi 8bf8e22448
Check card layouts / check (push) Has been cancelled
feat: publish A1-A8 card viewer and FreeRTOS C generator
2026-07-19 16:43:35 +02:00

165 lines
6.4 KiB
Markdown

# 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.