Skip to content

Concepts

Astrolabe has a small model. Once you know these terms, every guide and reference page uses them the same way.

Authors (people, editors or language models) write the source: HTML in the content format, with the answers in it.

<astl-doc lang="en">
<p>An elephant is <astl-gap id="g1" answer="grey"><astl-alt>gray</astl-alt></astl-gap>.</p>
</astl-doc>

engine.compile(source, { salt }) turns it into three artefacts:

Artefact Who sees it What it is
View students (public) HTML without answers: private attributes and children removed, ids replaced by opaque ids. Its SHA-256 is the viewHash, so it can be cached forever under /v/{viewHash}.html.
Answer key whoever grades (private) JSON: one entry per slot with its kind and key, the scoring groups, the pages and the viewHash it belongs to.
Metadata hosts and catalogues (public) Pages, slot and group counts, the open slots (answers for the teacher), maximum score, element counts, words of text.

Compiling the same source with the same secret and salt gives a byte-identical view and the same viewHash. Keys are immutable once published: the comparison rules are stored inside each key, so it grades the same way forever.

A slot is one answer. A gap has one slot; a closed question with one answer has one choice slot; a question with several answers has one flag slot per option. Answers are strings keyed by slot id:

Slot kind Answer
text, number what the student typed
choice the opaque id of the picked option
flag "true" when selected, absent otherwise
letter one letter of a crossword square, compared in capitals of the content language
open what the student wrote: stored with the attempt for the teacher, never graded

An answer is at most 500 characters, 2000 for an open slot, counted in Unicode code points (a Polish letter counts one); a longer one counts as unanswered.

Open answers. graded in a manifest says whether a type is scored. Graded types produce graded slots and add to the maximum. Ungraded types (graded: false) add nothing to it: they have no slots (flash cards, media), or only open slots (a long answer). Open slots are in the key (in no scoring group) and listed in Metadata.open; they get no verdict, the hand-in confirmation never counts them as empty, and the result says the answers went to the teacher.

Each graded component forms one scoring group with a rule, or one group per part when its definition grades it in parts (a crossword's words; a square where two words cross counts in both). Authors pick a mode with the scoring attribute; each component maps its modes to one of three rules:

Rule Score with n slots, c correct, P points
all-correct P when every slot is correct, else 0. Feedback shows only the group verdict, never which slot is wrong.
per-slot P for each correct slot.
proportional P x c / n, exact.

Groups get a verdict (correct, partial, incorrect or empty); slots get correct or incorrect when the student may see them. Results never contain key data, and solutions travel only when the policy reveals them.

What the student sees follows the rule:

  • all-correct shows the group verdict only, after a check and with the solution alike: a mark on each item would tell which ones are wrong, and the task is to get all of them right. The solution shows the right answers without marking the student's.
  • per-slot and proportional mark each item (a row, a statement, a card, a zone, a word): right, wrong, or, left unanswered, "no answer" in the dotted empty style, the same in every type, next to the marked ones.

An attempt policy says how one attempt may be played. Practice and tests are policies over the same content, not different content.

Field Meaning
mode practice: check any time. test: no checks, one submit. review: read only.
navigation free, or linear (no going back to earlier pages).
maxChecks practice only; null = unlimited.
deadline ISO 8601 or null. The server enforces it; the player shows a countdown.
passMark share of the maximum score needed to pass, 0 to 1, or null.
reveal what a submit returns: nothing, score, or answers (score and solution).
seed per-attempt shuffle seed, or null to keep the view order.

PRACTICE_POLICY and TEST_POLICY in @astrolabe/core are the two common ones. Whoever grades is authoritative: the player only shows the buttons the policy allows.

<astl-page id label> splits a document into pages. Pages are first class: each has a heading and a progress segment in the player, can be checked on its own (engine.grade(key, answers, { page })), comes back after a reload (snapshots store the page) and has its own counts in the metadata. A document without pages is one implicit page.

Two language settings never mix:

  • The UI locale is the reader's language: buttons, verdict words, announcements and issue messages. Set it with locale on <astl-player>, locale in createAuthoring, or the locale you pass to engine.describe.
  • The content language is the author's language: lang on <astl-doc> (or on any element inside). It decides grading (a decimal comma in Polish, Turkish dotless i, diacritics) and how screen readers pronounce the text.

An English exercise can have a Polish UI: the text is graded and pronounced as English (lang="en"), the buttons and verdict words are Polish. See Internationalisation.

Authors write readable ids (g1, q1, option a). Views never contain them: every id becomes an opaque id, k plus 16 base32 characters (80 bits of an HMAC-SHA-256 of the host secret, the compile salt and the source id).

  • The secret (at least 16 bytes) belongs to the host and stays stable: it decides every view.
  • The salt (for example the exercise id) keeps ids of two exercises apart.
  • Option ids and option order (shuffle) are derived from the same secret, so a student cannot map a view back to the source.

In the HTML-with-answers mode the secret is public on purpose (the answers are in the page anyway); a fixed one keeps ids stable across reloads.

source (with answers)
| engine.compile(source, { salt })
v
view (public) ----------------------> <astl-player> --- answers ---> Checker
key (private) --> whoever grades <------------------------------------+
engine.grade(key, answers) localChecker (browser) or httpChecker (protocol 1)

The player never holds answer logic: verdicts come from a checker. localChecker grades in the browser (HTML with answers, previews); httpChecker talks to a server through the grading protocol.