165 lines
6.4 KiB
Markdown
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.
|