modus
Menu

First milestone

Status: implemented on 2026-11-10. nix flake check passes.

This document describes what the code does now. It lists the choices that were made during the implementation and that the owner has not reviewed. It lists the gaps between the code and the documents. GLOSSARY.md defines the terms.

Evidence

  • kerneltool test starts the machine under QEMU and acts as the person through the QEMU monitor: keys, pointer moves, clicks.
  • Each step of the scenario waits for a text in the log of the kernel. A step that does not see its text in 20 seconds fails the test.
  • The scenario is a table in tools/kerneltool/machine.odin.
  • The test saves a picture of the screen at 52 steps. kerneltool screenshots copies them to docs/screenshots/.
  • nix flake check runs the same test in the Nix sandbox.

The scenario does these actions:

  1. 1. Wait for the kernel, the root service, the input service, the compositor and the console.
  2. 2. Type expressions and commands in the console: 1 + 2 * 3, a variable, length, hash("abc"), a type error, modules().
  3. 3. Start life from the console with start("life"). Drag its window.
  4. 4. Start monitor and paint from the menu of the bar. Choose colors and draw lines in paint. Start plasma with the launcher: the key super opens it, "pla" lists only plasma, Enter starts it.
  5. 5. Start fault. The kernel stops it on a page fault. The other programs continue.
  6. 6. Close plasma with its close button. Stop paint with stop("paint").
  7. 7. Call programs() and memory().
  8. 8. Call gcd(84, 36): the console has no such command. Call load("math"), then gcd(84, 36) + power(2, 10): the result is 1036. square_root(1000000) + square_root(-4) is a direct call of an export that fails: "error: square_root: the argument value has -4; it must have at least 0 (-1)", from the error record that the export wrote at the address that the console gave.
  9. 9. Call help().
  10. 10. Call isolate("probe"): the module runs in a call host. Call count(), checksum("abc" + name), and stop_machine with three handle numbers: the kernel refuses each one.
  11. 11. Call peek(0): the kernel stops the call host on a page fault, and the console reports an error with the stack trace of the stop report, one line for each frame ("error: frame 1: probe + 0x...", then the frames of host). Call count(): a new call host gives 1.
  12. 12. Call spin(), which never returns, then type count() + gcd(84, 36) during the call: the console keeps the keys. Press Escape: the console closes the handle of the call host, and the kernel stops it. The line that waited then runs: an isolated call and a direct call in one expression.
  13. 13. Call isolate("sample"): a record and a lent handle in an isolated call. r = Rectangle(1, 2, 30, 4) makes a record from the description of the module; area(r) gives 120; grow(r, 5) gives "Rectangle{x = -4, y = -3, width = 40, height = 14}", a Record result. b = buffer(1) makes a memory object of one page. fill(b, 3) gives 4096 and byte(b, 4095) gives 3: the module wrote in the memory object of the console. span_total(Span(b, 96, 10)) gives 30: a handle in a field of a record. spoil(b) gives 7 (No_Right): the description asks Read, and the module cannot map the object to write. keep(b) then kept() gives 2 (Bad_Handle): the handle ended with its call. area(Span(b, 0, 1)) is an error of type. Then load("sample"): the same descriptions serve a direct call, and g.width * g.height + keep(b) + kept() gives 12848. Then the revocation: hold(b) maps the lent memory and keeps the mapping; the console revokes what it lent at the end of the call; look() reads through the mapping, and the kernel stops the call host on a page fault. grants("monitor") prints "monitor has: log, machine". revoke("monitor", "machine") asks the root service: the log has "root: revoked the grant machine of monitor, holdings ended: 1", and monitor writes "no grant to inspect the machine" in its window and in the log. A second revoke is refused, and a grant with an unknown name too.
  14. 14. Start temple from the menu of the bar.
  15. 15. Start rings from the menu of the bar: six programs pass records through six rings. Add three records. Stop node 4: all the records wait in the ring before it. Start node 4 again.
  16. 16. Start threads from the menu of the bar: one program, five threads. A thread adds the numbers to 1000 and ends; the first thread reads 500500. Four threads draw one Julia set each in the surface of the program. Key 3 stops thread 3, then starts it again. programs() shows 5 threads.
  17. 17. Start workers from the menu of the bar. Four threads add to one number for 150 ms each: with the lock no addition is lost; with no lock, millions are lost. Then four threads take 140 tiles from one queue and compute a picture.
  18. 18. Start rights from the menu of the bar. The program holds the machine with the right Inspect and the boot image with the right Read. It asks the kernel for nine operations. The kernel does four and refuses five with the status No_Right: create a device handle, stop the machine, map the boot image to write, map it to execute, give it to the compositor. The log has "rights: ok, 4 allowed, 5 refused". Start origin from the menu of the bar: twelve cases of the origin of a handle (copies, a chain of copies, a mapping in another program, a revocation in one program only), each with the answer of the kernel. The log has "origin: ok, 12 cases" and the measure "origin: lend a handle and take it back: N ns with 4 kernel entries; copy and close: N ns with 2".
  19. 19. Start budget from the menu of the bar. The program creates memory objects of 64 pages until the kernel answers No_Budget, then closes them: its budget has the first number of pages again. It does the same with threads. It takes 256 pages for a new budget, creates 200 pages with it, and closes the new budget: its own budget then pays for the 200 pages. The log has "budget: ok, 10 memory objects then no budget, 3 threads then no budget, each page returned". Start limits from the menu of the bar: three budgets with a limit of processor time (5 %, 20 % and 60 % of one processor), one thread on each that computes with no end, and a fourth thread on the budget of the program, which has the limit of 1.5 processors. The log has "limits: processor time of one second, used of limit: 49642 of 50000, 198746 of 200000, 602592 of 600000, the program 1498231 of 1500000" (the numbers change a little at each run) and "limits: ok, 4 budgets within their processor time, 40 log records written and 60 refused with 40 for each second". Press windows in the bar: the menu has one line for each window. Press a line: the window comes to the front, and the log has "compositor: window console in front".
  20. 20. Start flow from the menu of the bar. One thread writes records in a ring and one thread reads them, for one second, four times. The log has one line for each measure: the records of one second, the kernel entries of the program for one million records, and "0 lost". On the machine of the test (QEMU with KVM, 4 processors): capacity 32 with a wait at once, 6.5 million records and 188 421 entries; capacity 1024 with a wait at once, 12.8 million and 70 774; capacity 32 with wait_spin, 29.2 million and 75; capacity 1024 with wait_spin, 29.8 million and 56. The test asserts the lines and "flow: ok, 4 measures, 0 records lost", not the numbers. Picture: flow.png.
  21. 21. Start processors from the menu of the bar. The machine of the test has four processors. Five threads of the program compute, and the program asks the kernel what each processor runs: the log has "processors: ok, 4 processors ran the program at the same time". Then 1, 2 and 4 threads compute the same picture, and the log has the three times: "processors: the picture took 404 ms with 1 thread, 198 ms with 2 threads, 148 ms with 4 threads" in one run. The test does not check the times. Then start locks from the menu of the bar: the measures of the locks of the kernel. It starts four locker programs; each one works only on its own objects. With 1, 2 and 4 lockers at one time it measures the kernel entries in one second (one cheap operation, Memory_Size, on an own memory object) and the pages on first use in one second (create, map, write one byte in each of 256 pages, unmap, close). Then two threads of locks wake each other with the word wait, alone, then while two lockers enter the kernel. The log has one line for each measure, the waits and the takes of each kind of lock for the measures with 4 lockers, and "locks: ok, the kernel counted each entry, each page returned, no wake was lost": the kernel counted each entry of each locker, each page returned to the budget of each locker, the free pages are the pages of the budgets and the reserved pages, and no wait ended at its deadline after its word changed. The test does not check a speed. Picture: locks. kerneltool measure runs the same measures with only the desktop. Then start scheduler from the menu of the bar: the scheduling strategies (docs/SCHEDULING.md). Under round robin, the fair strategy and EEVDF in turn, it measures four cases: two threads that wake each other while a spinner runs on each processor (wakes in one second); a thread that sleeps 2 ms, 60 times, among 8 spinners (how late it runs, in the middle and at worst); 8 spinners of one weight (the share of each, and the work in one second); 12 spinners with the weights 256, 512 and 1024 (the share of one spinner of each weight). The log has one line for each strategy with these numbers, the turns that a wake ended, the moves and the time of one pick. Then a thread changes the strategy 100 times while this program starts and ends threads, two threads wake each other and 8 spinners run: "scheduler: stress: 100 changes of strategy while N threads started and stopped, ...; after the last change each thread ran: 1; thread slots N of N". The program checks what does not depend on the load of the host: no spinner got less than a sixteenth of its equal part, EEVDF woke more often than round robin in the same run, the spinners of weight 1024 got more than those of weight 256 under the fair strategy and EEVDF, each thread ran after the last change, each thread slot returned, no wake was lost, the kernel refused Strategy_Set through a copy of the machine handle with Inspect only (No_Right) and a weight of 1025 (Bad_Operation). The log has "scheduler: ok, no spinner starved under 3 strategies, EEVDF woke N times as often as round robin, ...". The keys 1 and 3 change the strategy: "scheduler: the strategy is now round robin, it was EEVDF", then back to EEVDF. Picture: scheduler.
  22. 22. Start boot from the menu of the bar. The window shows the boot display: the seven stages of the start on one line of time, the memory that the firmware gave, the machine, the modules of the boot image, the programs. The log has "boot: ok, 7 stages, N memory ranges, N programs".
  23. 23. Start pages from the menu of the bar. The window shows what the page tables give, as the kernel reads it from the tables: the direct map, the kernel module, the kernel stacks, and the half of the program. The log has "0 write and execute, 48 guard pages of 48", "pages: ok, the page tables keep the rules" and "pages: the processor refuses the memory of programs to the kernel". Then start frames: ten cases of pages on first use and of shared code, each with the answer of the kernel, read from the page table of the program. A memory object of 8192 pages has no page frame after its creation and its mapping; a read gives zero and one frame; a write to each page gives 8192 frames, each zero before. A budget of 100 pages refuses 200 pages at creation, and serves each access to 99 pages when it has 0 pages left. A program twin writes a page that nobody used, and frames reads the word. Four twins with a copy of the module each, then four with the shared code: the log has "frames: 4 twins: 320 pages with a copy of the module each, 311 pages with the shared code (3 pages of the module, once)". A fifth twin writes to its code: the kernel stops it on a page fault, and the four others still run. The log has the measures and "frames: ok, 10 cases". Picture: frames.png.
  24. 24. Start paint from the menu of the bar, and call connect("paint") in the console. The log has "root: connected console to the service paint" and the console prints "paint: 4 commands of a service that runs". help() shows paint.line, paint.write, paint.clear and paint.lines with their parameters: the console read them in the file of the program. Call paint.line(40, 30, 440, 250, 1), a second line and paint.write(150, 20, "drawn by isolated calls", 0): the window of paint shows two lines and the text, and "callers 1 calls 4". paint.lines() gives 2. connect("life"), life.empty(), life.glider(10, 10) and life.population() give 5: the game continues with one glider. connect("monitor") gives "no service runs with this name". Stop paint: paint.lines() gives "error: no service runs with the name paint". Start paint again: paint.lines() gives 0 through a new connection. Picture: services.png.
  25. 25. Start log from the menu of the bar. The window shows the records of the log: the time, the program, the channel and the text of each one, and the count of records of each channel. The log has "log: the kernel wrote 7 records in the channel boot" and "log: ok, N records, 0 lost, N channels".
  26. 26. Start flood from the menu of the bar. The root service starts the program and its terminal: the log has "root: started flood, with a terminal". flood writes lines of four runs as fast as it can. The log has "terminal: flood wrote N lines in one second, N records, with N kernel entries for one million records; the terminal drew N pictures". Page Up holds the view: the log has "terminal: the view stays at line N, flood continues". The key Space goes to the program as a record: the log has "terminal: the first key goes to flood", "flood: the person pressed Space: no more lines after N", and after a second press "flood: the person pressed Space: the lines continue". stop("flood") in the console stops the program, and its terminal stays: the log has "terminal: flood stopped with code 0 after N lines and N records". The close button of the window stops the terminal: "root: terminal stopped with code 0". The console itself is a program with a terminal: each console line of this list is typed through the terminal, as key records. Four pictures: terminal, terminal_held, terminal_keys, terminal_stopped.
  27. 27. Start crowd from the menu of the bar. Five measures of one second each: 1 writer and 1 reader, 2 and 2, 3 and 1, 1 and 3 on a shared queue, then 2 and 2 on a queue with one lock. One event starts the threads of each measure. The log has, for each measure, "crowd: shared queue, N writers, N readers: N records in one second, N kernel entries for one million records, N written, N read, 0 wrong", then "crowd: ok, 5 measures, each record written is read one time, each event started all the threads". One picture: crowd.
  28. 28. Start clock from the menu of the bar. It reads the time as often as it can for one second, from the time page, then with a kernel entry. The log has "clock: the time page: N reads in one second, 0 kernel entries for one million reads", "clock: a kernel entry: N reads in one second, 1000000 kernel entries for one million reads", "clock: the time page and the cycle counter: in N reads the page was N to N ms behind and at most N ms ahead", then "clock: ok, the time never went back, the page and the kernel give the same time, the kernel refuses to remove the time page". One picture: clock.
  29. 29. Start tally from the menu of the bar: one module in each role. Type connect("tally"), then tally.add(5, "from the console, to the service"): the window of the program shows 5. Type load("tally"), then add(2, "in the console") + add(2, "in the console"): the console prints 6, the count of its own copy, and tally.total() still gives 5. Type unload("tally"), then isolate("tally"), then add(30, "in a sandboxed program") + adds(): the console prints 31. The three lines that name the module print the same hash. One picture: roles.
  30. 30. Start manual from the menu of the bar. The log has "manual: ok, N modules, N have their documentation". Type connect("manual"), then manual.signature("tally", 1): the console prints "add(amount: Integer, note: Text) -> Integer". Type manual.show("tally"): the window shows the page of tally, with its documentation, the four ways to use it and its exports. The keys up and down change the module. One picture: manual.
  31. 31. A module with imports, and the replacement of an import. load("lonely") gives two lines: "error: cannot load lonely" and "error: lonely imports 06711e49774633ea..., and the load source has no file with its hash". load("agenda") loads agenda and its import calendar; year_days(2000) + month_days(2, 2000) gives 393, because calendar has a fault (2000 is not a leap year for it). load("calendar") then unload("calendar") gives "the module stays: a module of the console imports it": one copy for the two. replace("calendar", "calendar_changed"): the root service writes "root: calendar_changed replaces calendar in each load that follows", and load("agenda") gives "error: agenda imports calendar, and its replacement calendar_changed fails the check" and "error: export days_in_month of calendar_changed: 3 parameters, calendar has 2". replace("calendar", "agenda") makes a cycle: "error: an import cycle: agenda imports calendar, and agenda still loads its imports" and "error: the replacement table gives agenda in place of calendar: that makes the cycle". replace("calendar", "calendar_fixed"), then load("agenda") prints the same hash of agenda and "the loader gave calendar_fixed ... in its place"; the same expression gives 395. A chain of replacements is refused. replacements() lists the table, and manual.show("agenda") shows the import and its replacement. Start imports from the menu of the bar: it imports calendar, and the root service gave it calendar_fixed. Its window shows twelve cases of the loader, each with the answer, and the log has "imports: ok, 12 cases" and the measures "imports: a call through an import slot: N ns to calendar, N ns to calendar_fixed in its place" and "imports: the bind of agenda: N ns, and N ns with the check of the replacement". The log also has "imports: two programs import calendar at two places: one copy of its code, 2 pages, mapped 4 times". programs() shows "modules calendar_fixed ... in place of calendar ..." for the console and for imports. isolate("agenda"), then month_days(13, 2000) stops the call host on a bounds check. The kernel writes "in module 5d8ff6e736fda684 at offset ..., which replaces b1f1b875df9992f8", and the console prints "error: the instruction is at offset ... of calendar_fixed ..." and "error: calendar_fixed ... runs in place of calendar ...": the fault names calendar_fixed, not calendar. Two pictures: imports, replacement. The information of the modules (property 12). At its start manual writes "manual: information: 45 modules and 65 exports listed in N us; who imports calendar: 2 modules, in N us; 0 modules break a rule". The console then combines the tables that the manual serves. columns(manual.importers("calendar_fixed"), "module name replacement slots") prints agenda and imports, each with the import calendar and the replacement calendar_fixed, and "2 rows of Import". columns(where(manual.running(), "module", "calendar_fixed"), "program slot module replaces") prints the program imports and the hash of calendar that calendar_fixed replaces: the module records of the kernel. sort(columns(manual.handles("Write"), "module name rights"), "name") prints sample, fill, "Read Write". join(columns(manual.running(), "program hash"), columns(manual.modules(), "hash name code exports"), "hash") prints each module that runs in each program, with its bytes of code and its exports: 31 rows in one run. manual.exports("nothing") gives "error: manual.exports: the argument name names none of the modules (-1)", and where(manual.modules(), "code", "large") gives "error: where: the field code is Integer, the value is Text". manual.overview() shows the table of all the modules in the window: the sizes, the counts of exports and imports, who imports each module and the programs that run it. manual.show("calendar_fixed") shows its sizes, the two modules that import it through the replacement, and the program that runs it in place of calendar. Two pictures: modules, module_information.
  32. 32. Start disk from the menu of the bar. The root service gives the disk of the measures (a virtio block device at the place 17 of the bus) to a new block service, and the block ring to disk: the log has "root: started block for the device at the place 17 of the bus" and "block: ok, 8192 blocks of 4096 bytes, a queue of 256, 85 requests in flight at most, flush: yes". disk gives its data region to the block service through the block ring, reads and writes blocks for one second in each of five measures, then writes 128 blocks, flushes, reads them back and compares. The log has one line for each measure and "disk: ok, 5 measures, 0 errors, write then read back: 0 wrong". In one run of the test: read, 1 block, 1 in flight: 13 237 blocks in one second, with 2 kernel entries of disk and 3 of the block service for each request; 32 in flight: 165 473 blocks, 0.05 and 0.09 entries; write, 1 in flight: 11 947 blocks; 32 in flight: 111 557 blocks, 0.06 and 0.09 entries; read, 16 blocks in each request, 8 in flight: 843 424 blocks (3.3 GB) in one second. The close button stops disk, and the root service stops its block service: "root: block stopped with code 0". Picture: disk.
  33. 33. Call connect("storage") in the console. No storage service runs: the root service starts it, with a block service of the data disk (a new disk of 16 384 blocks), then makes the connection. The log has "root: started block for the device at the place 16 of the bus", "root: started storage", "storage: ok, a new disk: generation 1, 16384 blocks" and "root: connected console to the service storage". The connection names the private store "console", with a limit of 4 blocks. storage.write("greeting", "kept on the disk") gives 2, the generation of the commit; storage.read("greeting") gives "kept on the disk". storage.write("second", "one block more") fails: "error: storage.write: the call needs 5 blocks in all; the limit is 4 blocks (-2)". storage.write("", "x") gives "error: storage.write: the argument name is empty; it must have 1 to 39 bytes (-3)", and storage.read("nothing") gives "error: storage.read: the argument name: the store has no object with this name (-7)": the sentences come from the error record of each call. storage.objects() gives "greeting 16 bytes, generation 2". The console acts for the person: storage.stores() gives "generation 2, 4 of 16384 blocks in use; console: 1 objects, 3 of 4 blocks, 1 connection". grants("console") gives "console has: log, machine, image, store"; revoke("console", "store") writes "root: revoked the grant store of console, connections changed: 1"; then storage.write("greeting", "changed") gives "error: storage.write: the caller has no grant for this: a private store (-1)", storage.stores() shows the store with "0 connections", and grants("console") gives "console lost: store". Picture: storage.
  34. 34. Start errors from the menu of the bar: 19 cases, each one an error made for real, with the sentence of its error record under it. Ten kernel operations (a right that lacks, a copy that was revoked, a handle that was closed, a handle of another kind, a number that names no handle, 64 pages from a budget of 8, a log text in the kernel half, a program name of 40 bytes, an unknown flag, the operation 99), two loads (a missing import, a replacement that fails its check), a direct call of math.square_root(-4), five isolated calls of the storage service (an empty name, a name of 40 bytes, a zero byte, a write over the limit of the store, an absent object), and the program fault, which faults three calls deep. The log has one line for each case, for example "errors: b use a copy after its revocation: Memory_Size: the argument memory, handle 0xa, was revoked", then "errors: ok, 19 cases, each with the expected error record". The tool reads the stack trace "errors: stack of fault: fault + 0x1444, fault + 0x15d9, fault + 0x15f9, fault + 0x1656" and names each frame from the linked file of fault: fault::three fault::two fault::one entry. The key b runs case b again. The log has the measures "errors: a kernel operation that succeeds: N ns; one that fails, with its error record: N ns; a direct call that succeeds: N ns; one that fails: N ns". Picture: errors.
  35. 34b. Start interface_elements from the menu of the bar: the log has the measures of the damage by difference ("interface_elements: nothing changed: 68 records, 0 damage rectangles, ...", "one number changed", "everything moved", "ok, the whole frame drawn in ... us; the next frame drawn in its ... damage rectangles in ... us"). Tab and Enter press its button ("the button was pressed 1 times"), Tab and the arrow down move its list ("the list chooses clock"), Tab and "hello" fill its text input ("the text is \"hello\""). Pictures: interface_elements, and interface_damage after its button "show the damage". At its start the compositor writes the measures of copy_bytes_without_overlap and fill_bytes_with_value; clock and remote write the time of one picture and the records and damage of the interface package.
  36. 34c. The text of each window of this test is the vector font (docs/FONTS.md): JetBrains Mono from the glyph cache of the boot image, with subpixel bands, at the scales 1, 2 and 3. At start the root service writes "root: the resource glyph_cache, 390624 bytes, its hash checked in ... us"; the boot display is drawn with it (the picture boot_fails is drawn by the root service). Start vector_glyphs: "vector_glyphs: one glyph from its outline, in fixed point, with the stripes (the least of batches): scale 1 ... ns, scale 2 ... ns, scale 3 ... ns; from the glyph cache: ... ns; Spleen 8x16: ... ns", and one line for each of the six candidate fonts ("the cache of ...: 95 characters at 3 scales built in ... us, ... bytes"). Picture: font_candidates. Start settings: "settings: ok, font vector, subpixel order rgb"; picture font_vector with the console, settings and monitor. In the console, connect("settings") then settings.set("font", "spleen"): the compositor writes "compositor: the settings changed: font spleen, subpixel order rgb; N windows draw again", each window draws again in Spleen 8x16 ("settings: drawn with the font spleen, ..."); picture font_spleen. From the host: settings.get("font") gives "spleen"; settings.set("font", "vector") and settings.set("subpixel_order", "bgr") give 1 and the compositor writes each change; settings.get("subpixel_order") gives "bgr"; settings.set("font", "comic") gives the error record -3 and settings.set("colour", "red") -2; settings.set("subpixel_order", "rgb") gives 1. Picture: settings. Host tests: coverage_is_the_area, the_last_column, hostile_files, filters_measured, golden_glyph_cache, damaged_glyph_cache (support/vector_font), blend_error_against_srgb, text_without_cache_is_spleen, glyph_draw_cost (support/graphics).
  37. 35. Call memory(). The console prints the pages in use, the reserved pages (paid, with no page frame yet), and "budgets N pages: all the free pages": after all the starts and stops of the test, each free page is in a budget or reserved. It also prints the number of processors, and for each kind of lock of the kernel the times that a processor took it and the times that it waited.
  38. 36. Call shutdown(). The machine stops with code 0.
  39. 37. A second machine starts with a boot image that has no compositor. The root service cannot start the compositor: the boot display stays on the screen with "the start failed: cannot start the compositor: status 1", and the machine does not stop. The log has "root: the boot display shows the failure". The test runs this machine first.
  40. 38. Eight more machines start with one data disk, new before the first one (the test of persistence, scenario_persist_*). First machine: start notes from the menu of the bar; the root service starts the storage service and connects notes to its private store "notes": "notes: a new store: no note, the count 0". Type "kept across a restart": each key is one commit. Press Tab: notes counts 1000, one commit for each step: "notes: count 1000, stopped: 1000 commits in 3224 ms". shutdown() stops the machine. Second machine: start notes: "storage: ok, generation 1022: 1 stores, 2 objects, 9 blocks in use, 7 blocks checked, 0 wrong" and "notes: restored the note "kept across a restart" and the count 1000, its 3 blocks agree". Picture: notes. Press Tab, and the tool ends QEMU (quit) after "notes: count 1100", while the commits continue: the machine does not stop. Third machine: the store has 0 wrong blocks, and "notes: restored ... the count 1106, its 3 blocks agree, the last run stopped before its end" (the count changes from run to run). Then the tool writes zero bytes over the second half of the root block with the highest generation, as a write that a stop cut. Fourth machine: "storage: the root of generation 1128 has a wrong check: a write that a stop cut; the service uses the root before it", "storage: ok, generation 1127", and the count of the commit before, 1105, with its 3 blocks in agreement. Then the tool changes one byte of the first block of data of the note. Fifth machine: "storage: 1 blocks have a wrong check: 1 objects and 0 stores are damaged; the service serves the others", the count is restored, and notes prints "WRONG: the storage service cannot give the note: the object is damaged" (picture notes_damaged); the keys "again" write a new note. Sixth machine: "0 wrong", and the note "again". Then the tool changes one byte of the block of data of the note that only the newest root names. Seventh machine: "storage: the newest root names a block with a wrong check, and the root before it names none: the root before it is used", and the note of the commit before, "agai". Then the tool makes a new disk with half of a first root. Eighth machine: "storage: the first root of the disk has a wrong check and no commit followed it", then a new disk.
  41. 38b. Two more machines test a loss of power (power_loss.odin, kerneltool test power). The kernel tool serves the data disk to QEMU itself, with the Network Block Device protocol, so it knows each write and each flush, in order. First machine, on a new disk: notes keeps the note "kept through a loss of power", then counts 1000 commits; after "notes: count 300" and a seeded count of writes (seed 20261111), the disk loses its power at the write of a root: it answers nothing more, and the tool ends QEMU. "the disk received 737 writes, 736 flushes and 378 reads; 1 writes came after the last flush". The tool then builds the images that a real disk can hold (the durable image, each prefix and each single write of the writes after the last flush, each of them torn after 1 sector, half of its sectors or all but one, and 300 seeded random subsets with torn writes) and checks each one with the code that the storage service runs at its start (support/storage_mount, on the host): "306 images of the disk after the loss of power, 72 of them with a torn write, each one checked with the code of the storage service on the host in 1162 ms: 227 give the durable commit, 79 the commit in flight, none a mix". "367 durable commits, and 736 flushes reached the disk: the block service passes each flush of the storage service to the device". Second machine, on the hard image (each write after the last flush lost, and the write of the root torn): "storage: the root of generation 369 has a wrong check: a write that a stop cut; the service uses the root before it", "storage: ok, generation 368", "notes: restored the note "kept through a loss of power" and the count 339, its 3 blocks agree". Picture: power_loss.
  42. 38c. The error records of the other calls (MODULE_CONTRACT.md, section 8, "A ring completion" and "The other calls"). Each family has a case of errors (the second page, picture errors_families): "v a wrapper: display.open of 0 by 100 pixels: display.open: the argument width has the value 0, which the call refuses; it takes at most 2048", "w a wrapper: terminal.open with no terminal: terminal.open: the caller has no grant for this: a terminal", "x a ring: root.Connect("storage"), not callable: root.Connect: the caller has no grant for this: a connection to this service", "y an export: tally.add, a note of 40 bytes: tally.add: the argument note has 40 bytes; it can have at most 36 bytes", "errors: ok, 25 cases". The block ring: "disk: a read of 2 blocks from the last block: block.Read: the argument first reaches 8193 blocks, past the end: there are 8192 blocks". The datagram ring: "net: UDP to 10.0.2.2 port 9: refused: datagram.Send: the argument address, 10.0.2.2 port 9, is refused: the grant names 10.0.2.2 port 7 only", "net: UDP of 2000 bytes: refused, no fragment: datagram.Send: the argument count has 2000 bytes; it can have at most 1472 bytes", and after the revocation "... is refused: the grant names no remote (it was revoked, or the remote is not admitted)". The root ring, from the console: "error: root.Start: the argument name names none of the modules", "error: root.Stop: the argument name names a program of the system: it starts at boot, or it is a device service, and the call does not act on it", "error: root.Revoke: the argument name names none of the programs", "error: cannot connect to plasma: root.Connect: the argument name names none of the services that run". The exports: "error: paint.line: the argument color has the value 9, which the call refuses; it takes at most 5", "error: add: the argument amount has -6; it must have at least 0". The measures of one failed call of each family, before and after (errors and net, one machine run each): a wrapper (terminal.open with no terminal) 0.4 ns, then 5.4 ns: the record is 56 bytes that it writes; a command of the root ring (Connect refused, the round trip) 21226 ns, then 20556 ns: the record is lost in the round trip; an export (tally.add with a note of 40 bytes, direct) 50.6 ns when it took the note and cut it, then 3.1 ns when it refuses it with its record; the datagram ring (a Send of 2000 bytes, Too_Large, the round trip) 1173 ns, then 1062 ns: seven values where there were two.
  43. 38d. Versions in the storage service (property 9; design in ARCHITECTURE.md, U5, "Versions"). The main machine: notes commits each key, and the storage service keeps the state before each commit: "notes: the note is "versions", generation 11, 7 versions; one commit of the note: 3778 us in the mean of 8"; Up twice: "notes: the version 7 of 8, generation 10: "version"", "notes: the version 6 of 8, generation 9: "versio"" (picture notes_versions: the bar has one mark for each version, the one shown is lit); Enter: "notes: restored the version of generation 9: "versio", in the commit of generation 12 in 5798 us; the note before it is a version". From the host (Call, through the remote service that acts for the person): storage.versions("notes", "note") gives "9 rows of Version" (generation, time, date, bytes, own_blocks, current); storage.version("notes", "note", 7) gives "vers"; storage.compare("notes", "note", 6, 11) gives one range, first 3, count 5; storage.version("notes", "note", 99) gives "error: storage.version: the argument generation: the object has no version of the generation 99 (-9)"; restore gives the commit 13 and clone("notes", "note", "copy") the commit 14. The console: "notes: 2 objects, 9 versions, 19 of 4500 blocks, 2 shared, 1 connection" (picture storage_versions: the clone shares its list and its block). The limit of a store: the console has 4 blocks; "storage.write("greeting", "changed at the limit")" gives the commit 3 and "storage: the store console is at its limit of 4 blocks: the commit of generation 3 thinned 1 version, the oldest of the store first"; a change that the current objects alone cannot hold: "error: storage.write: the call needs 5 blocks in all, with no earlier version kept; the limit is 4 blocks (-2)". A list of 1000 versions (the count, after 1000 commits): "notes: the versions of the count: 1000 rows in 50544 us". The two losses of power (kerneltool test power, the crash search of 38b with each version checked byte for byte): before the limit, "306 images ... in 2456 ms: 227 give the durable commit, 79 the commit in flight, none a mix; each object and each of its versions byte for byte (339 versions durable, 340 in flight, 0 thinned by the commit in flight)"; during a thinning at the limit of 4500 blocks, "306 images ... in 6385 ms ... (1119 versions durable, 1119 in flight, 1 thinned by the commit in flight)"; then "storage: ok, generation 1247: 1 stores, 2 objects, 1119 versions, 4500 blocks in use". The retention rule: 6 host tests (odin test support/storage_retention).
  44. 38e. The date. The program date reads the real-time clock at its start: "date: ok, 2026-11-11 16:45:42 UTC, seconds since 1970 1794415542, from the real-time clock at the time 638 ms of the system; the date of the time 0 in 598085 us" (it waits for the clock to change its second). The tool compares the date of the machine with the clock of the host (QEMU: -rtc base=utc): at the start "the date of the machine ... is 1794416273 s since 1970, the host gives 1794416273: 0 s apart"; about five minutes later, from the host, date.now() gives "Date{milliseconds = 1794416596428, year = 2026, month = 11, day = 11, hour = 17, minute = 3, second = 16, weekday = 3}", "3 s apart" (the date advanced with the host; the tolerance is 5 s). The HTTP server sends the field Date, which the TCP of the host reads: "the field Date "Wed, 11 Nov 2026 17:14:55 GMT" of the answer to / ... 1 s apart", the same for /large and /sink. The versions have the date of their commit: "notes: the version 7 of 8, generation 10, 2026-11-11 17:03:15: "version"". The bar shows the date and the time of day in UTC in each picture. The calendar: 4 host tests (odin test support/calendar), with each day from 1900 to 2500.
  45. 39. Six more machines start, each with one test of the kernel on itself. The kernel does one thing that its page tables must refuse: it uses more than its stack, it writes to its code, it writes to its code through the direct map, it executes its data, it reads the memory of a program outside a copy, it executes the code of a program. Each time the processor refuses, the kernel reports the fault with its reason ("the kernel stack of the processor is full", "write to a read-only page", ...), and the machine stops with code 2.
  46. 40. One more machine starts for the network (scenario_network). Its virtio network device sends each frame as one UDP datagram to the peer of the tool, on the loopback of the host: the peer is the gateway 10.0.2.2 and an echo server on port 7. Start net from the menu of the bar. The root service starts the frame service of the device at the place 18 of the bus, then the network service, then net with one endpoint: "frame: ok, the hardware address 52:54:00:12:34:56, the link is up", "network: an endpoint for net: the local port 49152, the grant 10.0.2.2 port 7". net shows ten cases, each with its answer: the address 10.0.2.15/24 and the link; ARP: the gateway answers 52:55:0a:00:02:02; an echo of the gateway, in 192 us in one run; a datagram of 41 bytes to port 7 and the same 41 bytes back; a datagram to port 9, refused with "the grant gives 10.0.2.2 port 7 only"; an echo of 10.0.2.3, refused; a datagram of 2000 bytes, refused with the limit 1472. The peer sent 11 malformed frames before its first answer. One came to the local port of net from the remote port 9, that its grant does not name: a row of this endpoint, "refused: from port 9; a row of this endpoint". Ten have no endpoint (a short frame, IPv6, a wrong IPv4 checksum, a header of 16 bytes, a total length after the frame, a fragment, a wrong UDP checksum, a UDP length after the packet, a port of no endpoint, ARP with a hardware address of 8 bytes): rows of the machine, that net reads because its grant has the right Inspect: "10 refused in 10 kinds, 10 of 10 expected; no fault". The log has the sentence of each Problem. The network service answered the echo request of the peer. The log has "net: ok, 10 cases" and the measures: "net: the round trip of one datagram of 64 bytes: 60 us at least, 83 us in the middle, over 1000", "net: a burst with 64 datagrams in flight: 81733 datagrams in one second, sent and answered, 0 lost", "net: kernel entries for each datagram: 0.21 of this program, 0.51 of the network service, 0.98 of the frame service" (one run). Picture: network. Then grants("net") gives "net has: log, machine, network", revoke("net", "network") gives "root: revoked the grant network of net, endpoints changed: 1", and a key in net sends one more datagram: "refused: the endpoint has no grant now". The close button stops net, its endpoint ends, and memory() gives "budgets N pages: all the free pages". After the machine stops, the tool checks the peer: each frame from the machine was correct (the peer reads the frames with its own code), and the machine answered its echo request: "the peer received 60659 frames from the network service, 0 wrong" in one run. The firmware also uses the device before the kernel starts: the peer reads strictly from the first ARP frame of 10.0.2.15, and counts the frames before it. At its end the person closes remote and manual, the launcher starts life and plasma, and the tool places four windows: the picture desktop. Then the launcher lists 6 names for "p", the names that start with "p" first; the arrow down chooses plasma (the picture launcher); Escape closes it. "xyz" lists 0 names, and Enter starts nothing (the picture launcher_empty); the key super closes it.
  47. 41. On the same machine, calls from the host (MODULE_CONTRACT.md, section 4, "A call from the host"). Start monitor, manual, then remote from the menu of the bar. The root service gives remote an endpoint whose grant names 10.0.2.2 port 7100, and a ring that serves Connect only to manual, monitor and storage: "remote: ok, an endpoint with the grant 10.0.2.2 port 7100". The peer sends six malformed call requests, and remote refuses each one with the sentence of its error record: "remote: the call has 20 bytes; it must have at least 112 bytes", an unknown kind, more bytes of arguments than the datagram has, a result past the end of the call region, an error record that is not aligned, and a service that the root service does not let it call ("the caller has no grant for this: a connection to this service"). Then the steps Call of the tool: monitor.count("processors") gives the Integer 4; monitor.programs() gives a table of 10 rows of Program with the row of remote, and the monitor wrote "monitor: programs: 10 rows"; monitor.counts() gives 13 rows of Count; monitor.served() gives the Record Served{calls = ..., callers = ..., pictures = ...}; manual.modules() gives 52 rows of Module in 9 datagrams; storage.stores() gives the Text "generation 2, 5 of 16384 blocks in use; console: 1 objects, 3 of 4 blocks, 0 connections" (the root service starts the storage service at the first connection). Failures give the sentence of the error record of the service: "error: monitor.count: the argument name names nothing that exists (-3)", "error: storage.size: the caller has no grant for this: a private store (-1)", "error: manual.exports: the argument name names none of the modules (-1)". monitor.count(4) is refused on the host, before a datagram: "the parameter name of monitor.count is a Text, the argument is an Integer". The tool measures a small call (monitor.count) from the host: in the last full check, a round trip of 181 us at least and 191 us in the middle over 1000 calls, and 5088 calls in one second one after the other (an earlier run: 172 us, 202 us and 4734 calls). The round trip of a datagram of net in the same machine is 52 to 60 us at least: a call adds the isolated call from remote to the service, and on the host the socket of the tool and the thread of the peer. The window of remote drew one picture in 119 us. grants("remote") gives "remote has: log, network, person"; revoke("remote", "person") gives "root: revoked the grant person of remote, connections changed: 3", and then monitor.programs() gives "error: monitor.programs: the caller has no grant for this: to act for the person (-1)", manual.running() the same (-3), and storage.stores() "no grant to see the stores"; manual.modules() still works. Picture: remote. After the machine stops, the tool checks that remote refused each malformed request with the expected field and kind. The second layer (the network machine): the peer drops the datagram of the answer of monitor.count("processors"); the tool sends the request 2 times, "remote: the request ... came again: its answer went again, and the call did not run again; 1 in all", and monitor.served() counts 2 calls across the step (the call and its own). A request with a changed hash: "error: the machine runs monitor <hash>; the request named <hash>". compositor.pointer() and compositor.windows() give the place of the pointer and the windows; after revoke("remote", "person") the compositor refuses them, and the tool moves the pointer with no confirmation again. monitor.counts() gives 15 rows, with the scheduling strategy.
  48. 42. The main machine launches remote after workers (a new window before that moves the places of the windows that the steps press at fixed places). From then on the tool reads compositor.pointer() before each press and moves the pointer again until it is the place of the step: in the last full check, 120 places confirmed on the main machine, 0 after a correction, one confirmation 257 us at least and 299 us in the middle (2 places on the network machine: 305 and 350 us); 3.7 ms before the tool kept the module files, when it read and hashed the file of the compositor at each call. The calls per second did not change: a small call took 173 us at least and 190 us in the middle, 5128 calls in one second (5088 in the first layer). log.records(0) prints the log records as a table, and 11 checks of the scenario are steps Record: the program, the channel and the level of the record, and a part of its text, for example root, devices, info, "root: started block for the device at the place 17 of the bus".
  49. 43. A developer tool (docs/TOOLS.md). At the end of the main machine, while errors still holds the program fault that stopped, the scenario starts monitor again (its machine grant was revoked above) and launches inspect. The letters "threads" choose the program threads: "inspect: threads: 18 mappings, 327 mapped pages, 204 with a frame, 5 threads, 1 modules; the kernel says 327 mapped pages and 5 threads", "inspect: one sample of threads took 102 us and 68 kernel entries". The step Agree checks that inspect.mappings("threads") and inspect.threads("threads") give the mapped pages and the threads that monitor.programs() gives: 327 pages in 18 mappings and 5 threads; for console, 2241 pages in 24 mappings and 2 threads. Picture inspect: the address space of console as a band (module code, constants and data, shared rings and regions, the boot image as device memory, the module math that the console loaded, the thread areas), the table of the mappings with the pages that have a frame, the modules, and the two threads with their state, home queue, weight, request, lag and processor time. Space freezes the sample and starts it again. The letters "fault" choose the stopped program: "inspect: fault stopped: page fault at 0x0; stack: fault + 0x1454, fault + 0x1479, fault + 0x1499, fault + 0x16c6, near entry + 0x46"; picture inspect_stopped. Tab shows the bytes of the memory of inspect: the code of its export entry, and four pages of a memory object of which two were written; the two others have no page frame and the tool does not read them (picture inspect_hex). From the host: inspect.mappings("console") prints 24 rows of Mapping with "console + offset", the kind, the rights, the memory object and its names; inspect.threads("threads"), inspect.modules("console"); inspect.mappings("nothing") gives "error: inspect.mappings: the argument program names none of the programs (-2)".
  50. 43. One more machine starts for TCP (scenario_tcp, docs/NETWORK.md), with the peer of the tool on -netdev dgram. Start server from the menu of the bar. The root service gives it a segment endpoint with a listen grant: "network: an endpoint for server: TCP, the listen port 80, the remotes that send to it". The step Peer asks the peer for one case at a time; the peer is a TCP client of server with support/tcp, and it changes the frames with a generator that the case seeds. The cases: a page and a close from the peer ("closed, the remote first"); 1 MiB of /large from the server and 1 MiB to /sink, each checked by its FNV-1a hash, with a close from the server ("closed, this server first"); the two again with 5 % of the frames dropped in each direction, with 10 % reordered, with 10 % sent two times; a window of the peer at 0 for 1.5 s ("4 probes"); a RST of the peer after 100 KiB ("reset by the remote"); an ACK of no connection, which gets a RST; a RST in the window and a SYN on a connection, which get challenge ACKs, and the connection serves one more request ("2 challenges"); 16 connections at once ("server: 16 connections open at once"). grants("server") gives "server has: log, network". The close button stops server, the endpoint ends, and memory() finds each page. The peer checks each frame of the machine with its own code, also the TCP checksum: "0 wrong". Picture: server.
  51. 44. One more machine starts for TCP against a stack that this project did not write (scenario_tcp_real): QEMU user networking, whose TCP is the one of libslirp (from 4.4BSD). The step Fetch makes the TCP of the host fetch /, /large and /sink of server through hostfwd (a port of the loopback of the host to the port 80 of the machine), and checks the hash: "the TCP of the host fetched /large from server through libslirp: 1048576 bytes, fnv1a64 e7867887f2822325 as expected". Then start("fetch") in the console: fetch has a client grant of 10.0.2.100 port 80, which guestfwd gives to a listener of the tool. Its eight cases: the grant; a SYN to 10.0.2.2, refused by the network service with the grant; a SYN from a local port out of its block, refused; the handshake; GET /hello; GET /large, 1 MiB with its hash; POST /sink, 1 MiB, and the listener gives the same hash; the close from this side. The log has "fetch: ok, 8 cases". Pictures: fetch, server_real.
  52. 45. On the machine with libslirp, after fetch: connect("server"), then server.put("hello.txt", "kept in the store of server"): the log has "server: the page /hello.txt is in the store: 27 bytes", and the TCP of the host fetches /hello.txt with the same 27 bytes. server.pages() lists it. Then the step Http runs HTTP/1.1 from the TCP of the host (tools/kerneltool/http_host.odin): 1000 requests on one connection (the log has "server: 1000 requests on one connection of 10.0.2.2 port N in N ms: N requests in one second"); 300 requests with a new connection each; three requests in one segment, with three answers in order (GET / 200, HEAD /counts.json 200 with no body, GET /nothing 404); a head in 2 sends and a body of 200 000 bytes in 10 sends 20 ms apart, with the same hash back; HEAD / then a request on the same connection; 16 connections at once; each refusal with its status (no Host 400, a bad request line 400, PUT 405, DELETE 405, 404, a target of 1100 bytes 414, 71 fields 431, a body over 16 MiB 413, an unknown method 501, a chunked body 501, HTTP/2.0 505, half a head 408 after 10 s). server.remove("hello.txt"), then /hello.txt gives 404. Picture: server_real (both tables of the window live).
  53. 46. The pointer pictures (docs/POINTERS.md). The build converts the theme of the owner, Posy's Cursor, from the Xcursor folder of the flake input posy-cursors (kerneltool pointers: "pointers_posy: 15 shapes, 178 pictures, 883008 bytes, from resources/pointers/posy.manifest in 34.4 ms"), and the theme of the system from the grids of resources/pointers/system.manifest (42 pictures, 208 512 bytes, 0.7 ms); both are resources of the boot image. The root service checks the hash of the resource and gives it read-only to the compositor: "root: the resource pointers_posy (883008 bytes, its hash checked in 1810 us) is given to compositor, read-only". The compositor validates it once: "compositor: the pointer theme Posy's Cursor (CC-BY-NC-4.0): 15 shapes, 178 pictures, 883008 bytes, validated in 653 us", and measures its blend: "the blend of the arrow of 32x32 (860 of 1024 pixels transparent): 965 ns; a copy of the same size: 153 ns", and the pointer in 256 frames: "2040 ns for a frame in the mean". The terminal asks for the shape text over its surface. At the end of the main machine the scenario starts pointers, and 11 steps Point move the pointer with no press and read the shape in compositor.pointer(): move over the title bar of pointers, pointing_hand over its close button, arrow over its border, wait over the tile of wait (the request of the program: "compositor: pointers asks for the pointer shape wait over its surface"; an animated shape of 77 frames), arrow and move over two other tiles; a press on the second button shows the theme of the system, and help over its tile; then pointing_hand over the button of the programs in the bar, arrow over the bar, text over the surface of the console and over the input line of the launcher. Pictures: pointers (the theme of the owner, the pointer wait of Posy over its tile), pointers_system (the theme of the system, the pointer help), pointer_text (the shape text over the console). kerneltool check builds the two theme files two times: "62 modules and 2 pointer theme files are the same bytes after a second build". The first nix flake check of the branch ran each machine test and then failed on "console.module / root.module is not the same bytes after a second build", the fault that choice 359 corrects; the second one passed. The numbers are from one run with a load of about 30 on the host, the measures of speed of this run are not valid (rule of the brief: a load above 8).
  54. 47. An independent read of the HTTP server (/tmp/review_http.md, 2026-11-11): each finding has a test that failed on the code before (a scratch build of the old code with the new tests, which went on after each failure). On the machine of the peer: "one byte after a request" (B1: the server ends it after 10 s with 408 and its FIN), "answers then close to a zero window" (F3: a RST after 10 s), "body then nothing" and "slow reader" (W6: a RST, the log has "aborted: no byte of the body in 10 s" and "aborted: an answer under 4096 bytes in one second"), "page changed during its answer" (B2: 40 pipelined answers of a page of 3200 bytes; the console removes the page with server.remove; the peer reads only after that; the log has "aborted: the page changed during its answer" and a page comes after it: before, the program stopped at a bounds check). On the machine with libslirp: "body-not-taken" (F4: one answer, then the end), "requests-then-fin" (W7: three answers), "headers" (nosniff, and Connection: keep-alive for HTTP/1.0), the line of the ends of connections not on the log (W10), and after revoke("server", "store") a put and a remove that the store refuses while /kept.txt keeps its bytes (F5, W8). Host tests: the absolute form, HTTP/1.2 and empty lines before the request line (support/web), the entries of the page index and the escape of HTML, the place of a remote at 60 s and at 120 s (support/network). The case leak sends 192 ports of each kind: with 64 it passed on the old service (a scratch run).
  55. 48. Randomness (docs/RANDOMNESS.md). kerneltool test random starts three machines. Two have the processor of the host: the log has "root: the random seed of the system, strength entropy (rdseed): rdseed present, 8 good values, 0 refused; rdrand present, 8 good, 0 refused". One has -cpu max,-rdrand,-rdseed: the log has "strength weak (the cycle counter only): rdseed absent ...", and each program still starts. The program randomness shows the facts of the seed and its own generator (pictures randomness and randomness_weak). The first 16 bytes of the stream of 4 programs in the 3 machines all differ (the steps Remember and Distinct). kerneltool cryptography-origin (in check) proves that the 4 files of ChaCha20 in support/cryptography are their origin in Odin dev-2026-05 with the listed import changes only; the RFC 8439 vectors pass on the host.
  56. 49. Tables and bytes as parameters (MODULE_CONTRACT.md, "Tables and bytes as parameters"). In the console, after load("sample"): t = readings(4), sum_values(t) gives 100, scale_values(t, 7) changes the rows of the variable in place (t prints 70, 140, 210, 280), sum_values(where(t, "sensor", 2)) gives 210, and sum_values(columns(t, "sensor value")) gives "error: sum_values: argument 1 is Table(Reading, Read), not Table(Reading)"; after isolate("sample") the same rows go into the call region and scale_values(t, 3) then sum_values(t) gives 300. manual.signature("sample", 15) gives "sum_words(words: Table(Word, Change), text: Bytes(Read)) -> Integer". From the host: kerneltool call "monitor.counts_named([(\"programs\"), (\"processors\")])" gives 2 rows of Count, a Change table is refused with its sentence, and sample is refused: remote cannot reach it. The program tables calls 8 cases in three ways with the same arguments: a direct call of sample loaded in its memory, a call host of sample, and the service sample (the same module, which the root service starts as a program): each answer is the expected one, also the guard after the capacity of an Append table, which an export that writes past its capacity overwrites in a direct call only. Five hostile cases through the call host, and the measures of 64, 1024 and 65536 records of 40 bytes. The program interface_through_module builds one frame through the module interface_module and with the package compiled in: the two draw tables are equal. Pictures: tables, table_argument, manual_tables, interface_module.
  57. 50. The module cryptography (docs/CRYPTOGRAPHY.md). kerneltool cryptography-origin (in check) proves that the 37 taken files are their origin in Odin dev-2026-05 with the 6 listed import changes only. odin test support/cryptography/vectors runs the vectors of RFC 8439, FIPS 180-4, RFC 4231, RFC 5869, RFC 7748, RFC 8032 and the GCM test case 4 on the host. kerneltool test crypto starts cryptography_vectors, which imports the module and runs the same vectors through its exports on the machine: the log has "ChaCha20-Poly1305 (RFC 8439 2.8.2): passed; a changed byte gives Not_Authentic" and the same for AES-128-GCM, X25519 and Ed25519, then "7 of 7 vectors passed" with the BLAKE3 hash of the module. Picture: cryptography_vectors.
  58. 51. The line of the console (docs/CONSOLE.md). At the end of the main machine, the person types mcnt in the console: the completion list opens while the keys come, Escape closes it, Tab opens it again, and the log has "console: the completion list opens by Tab: N names of M match \"mcnt\", the first is monitor.count; one ranking takes N us at most, over K keys" (picture console_completion). Enter takes the first name with its (: "console: the completion gave the line monitor.count(". The caret: "acdf", then Home, Right, Right, b, End, Left, Left, Delete, Backspace, e, Enter prints abce. The history search: two lines with length(, Control-R, leng, Control-R, Control-S, Control-R, Enter: "console: the history search gave the line length(\"first search line\")", and a second Enter runs it (picture console_history_search). The host tests of support/line_editor pin each key, a full line, an empty line, the completion places, the call at the caret, the order of five queries over real names, 10 000 candidates, hostile names and a hostile history record. The measure of one key with the list open: measure_completion() writes "console: one key with the completion list: the collect N us, the rank N us, the rows N us, over K names" for the names of the console and for 512 names of 128 bytes, the least of 20 batches of 10 keys, in the main machine and in kerneltool measure. The host tests of support/line_editor also pin the line history file (a round trip, the newest lines in one block, ten hostile files), and those of support/terminal the form of a hint line for each width. The line history across a stop: the first machine of persistence types length("kept across a stop") and shutdown(); the second one says "console: the line history: 4 lines kept from before the stop", Up and Up give the line and Enter runs it (18), and Control-R kept finds it.
  59. 52. The extended state of the processor (ARCHITECTURE.md, "The extended state of the processor"). kerneltool test extended_state starts three machines. With -cpu max the log has "kernel: extended state: x87, SSE, AVX; one save writes at most 832 bytes, in an area of 2688 bytes in each thread entry; saved at a switch of threads with xsaveopt"; the tool reads the host (avx512f, avx512bw in /proc/cpuinfo, and /dev/kvm) and waits for the line with "AVX-512" and 2688 bytes when the host gives it. The program extended_state starts as many threads as processors plus two (31 at most); each holds ymm0 to ymm15 (or zmm0 to zmm31 and k0 to k7, 64 bits of each with AVX-512 BW), the x87 stack, the x87 control word and MXCSR with a pattern of its own for each pass, across 16 kernel entries of each pass (half of them give the processor away), for 3 s; in round 5 only MXCSR changes, in round 10 only the x87 control word; it checks each register and the start state of the thread (picture extended_state). With -cpu max,-avx the kernel saves x87 and SSE with xsaveopt (576 bytes) and the program holds xmm0 to xmm15 (picture extended_state_without_avx). With -cpu max,-xsave the kernel saves with fxsave (512 bytes). kerneltool measure prints one empty kernel entry with xmm, ymm and zmm in use. Host tests: support/instruction_sets (the usable instruction sets from CPUID and XCR0). Measured by the coordinator on the machine without xsave: "6 threads on 4 processors, 2250 passes, 36000 kernel entries", an empty kernel entry 142 ns with xmm in use. Since the change of 2026-11-12 (550), a kernel entry saves nothing: the threads of extended_state hold their registers through each entry with no save, which checks that the kernel itself changes none, and through each switch to another thread of the test on the same processor (each wait of an odd round leaves the processor to one of the other threads: there are two more threads than processors). The build refuses a kernel with an x87, MMX, SSE or AVX instruction outside the save and the restore (551). kerneltool measure also prints one hand-over between two threads by the word wait with x87 and SSE in use and with the ymm high halves in use, and the cycles of each instruction of the save and the restore, measured by the program on its own area. Measured by the coordinator (kerneltool measure, two runs of each build, host load 6.4 to 8.7): kernel entries in one second with one locker 8 063 360 and 8 359 466 (124 and 120 ns) against 5 669 546 and 5 613 013 on master (176 and 178 ns); with 4 lockers 26 945 066 and 27 561 173 against 20 326 826 and 20 503 040; one hand-over between two threads 128 and 125 ns with x87 and SSE in use, 133 and 127 ns with the ymm high halves in use.

The path from power to desktop

  1. 1. The firmware starts the boot program (boot/), a PE file.
  2. 2. The boot program reads the kernel module and the boot image from the boot volume. It loads the kernel with the code of contract/module/. It collects the memory map and the framebuffer in a Boot_Info record, leaves the firmware, and jumps to the kernel.
  3. 3. The kernel (kernel/) takes the memory, builds its tables, sets the processor tables, the interrupt controller and the timer. It reads the list of the processors in the ACPI tables and starts each one.
  4. 4. The kernel loads one program from the boot image: the root service. It gives it three handles: the machine, the framebuffer, the boot image.
  5. 5. The root service (programs/root) loads the other programs in user space with system.program_start. It starts the input service, the compositor and the console. The boot display (support/bootscreen/) is on the screen from step 2 to step 5. The boot program draws it with what it knows. The root service draws it again before each start, with the records of the kernel. The compositor then takes the screen.
  6. 6. From this point, the kernel only executes kernel operations, schedules, and turns interrupts into wakes.

Kernel

SubjectNow
EntryOne entry point: the syscall instruction. rdi is 0, a deadline in milliseconds, or WAIT_FOREVER. rax returns the time
Kernel operations26 command kinds in the kernel ring of each thread. contract/operation/operation.odin is the full list
ObjectsMemory, Program, Thread, Interrupt, Ports, Machine, Budget, Log
BudgetA budget holds pages, programs and threads. Memory_Create, Program_Create and Thread_Create take a budget handle with the right Spend. The budget pays for memory objects, thread areas, page tables and slots. Budget_Create moves a part of a budget to a new budget. What an object used returns to the budget that paid. A budget with no handle ends: what it holds returns to its parent, and its parent pays for its objects. The kernel creates one budget at boot, for the root service: all the free pages and all the slots. The sum of the pages of all budgets is always the count of free pages; System_Report gives both numbers
LogLog takes a Log handle with the right Write. The root service gives it to each program that it starts
RightsEach handle slot holds a set of rights: Give, Read, Write, Execute, Control, Inspect, Devices, Stop. The handle of a new object has each right of its kind. Handle_Give(target, handle, Rights) copies a handle with the rights that are in both sets. No operation adds a right. An operation on a handle without the needed right gives No_Right
TablesFlat and fixed: 2048 memory objects, 64 programs, 256 threads, 128 handles and 128 mappings per program, 32 threads per program
ThreadsA thread is a register state and a thread area: the thread record and the kernel ring of the thread. Thread_Create(entry, stack, parameter) adds a thread to the caller. The threads of a program share its memory and its handles. A program with no thread stops with code 0
MemoryA memory object is pages with a frame list and a reference count. Memory_Create reserves: the budget pays for each page now, and each page gets its zero page frame at its first use, by a page fault or by the kernel. Memory_Map maps it in a program. Device memory, thread areas and the first program are contiguous
WaitA thread waits for a time, or on words (the word wait). The kernel has no other wake. For an interrupt, the stop of a held program and the end of another thread, the kernel adds 1 to the event word of each thread of the program, and wakes the threads that wait on that word
Word waitWord_Wait(address of Word records, count) and Word_Wake(address, count). The kernel names a word by its page frame, so the word can be in memory that two programs share. One entry can wait on 64 words. system.Lock and the wait of a ring are built on it
SchedulerOne run queue for each processor. Three strategies behind one table of six functions: round robin, fair (virtual time and weights) and EEVDF (the strategy at boot); a program with the right Schedule changes the strategy while the machine runs (Strategy_Set). Wake preemption, also on another processor. A time slice of 10 ms is the longest turn. A woken thread goes to the queue of the processor where it ran last; an idle processor, or one with less load, takes a thread from another queue. Idle is hlt: a processor that makes a thread ready wakes an idle processor with an interrupt
ProcessorsAt most 64. Each processor has a processor record: its kernel stack, its task state with the port bitmap, its segment table, its current thread, its counts. No lock covers all the kernel: a lock protects one kind of data (ARCHITECTURE.md, "Locks of the kernel"). Each processor has its own timer at 1000 Hz (local APIC); the first processor counts the time. The 16 device lines stay on the 8259 and interrupt the first processor
Address translationsBefore the kernel frees a memory object or a page table, each other processor that uses it passes a point where it reloads its table (processor_sync). A running thread is never taken from its processor: the processor ends it
DevicesA Ports object opens a port range in the port bitmap of the program. An Interrupt object counts interrupts and wakes its owner: a line of the 8259, or a message interrupt with a vector of its own. Device memory is a memory object over the registers of a device, mapped with no cache. Memory_Physical gives the physical address of a memory object to the holder of the right Physical
FaultsA fault in a thread stops its program with a code. The kernel wakes each program that holds its handle
Life of a programA program runs while a handle names it. When its last handle closes, the kernel stops it. Only the root service has no handle
MachineOne Machine object with four rights. Inspect: read System_Report, Program_Report and Processor_Report records. Devices: create device handles. Stop: stop the machine. Physical: read the physical address of a memory object. The root service keeps Devices. monitor receives Inspect. The console receives Inspect and Stop. A block service and a frame service receive Physical

The kernel contains no code for a device, except the timer, the interrupt controller and the serial port of its log. The block service of a virtio disk is a program (programs/block), and so is the frame service of a virtio network device (programs/frame).

User space

PartRole
support/system/Wrappers of the kernel operations, text formatting, the loader with its table of prepared modules, the grant roles
contract/ring/Command queue and completion queue in one memory object
support/display/The protocol of the compositor (create a surface, present, key and pointer events) and the client code
support/graphics/Canvas, fill, line, copy, text. The text is the vector font of the glyph cache, or Spleen 8x16 at its native size (the setting font, docs/FONTS.md)
programs/rootRoot service: starts programs, grants by name, answers Start and Stop, reclaims stopped programs
programs/inputInput service: PS/2 keyboard and mouse through granted ports and interrupts
programs/block, contract/block, support/pciBlock service: one virtio block device, through device memory, a message interrupt and a block ring. The root service finds the device on the bus and gives it
programs/diskThe measures of the block service, on the disk of the measures
support/virtioWhat each device service of a virtio device does the same way: the capabilities, the features, the split virtqueues. The block service and the frame service use it
programs/frame, contract/frameFrame service: one virtio network device, through device memory, a message interrupt and a frame ring. It does not read a frame
programs/network, contract/datagram, support/networkNetwork service: the one client of the frame service. Ethernet, ARP, IPv4, ICMP echo and UDP; one endpoint for each program with the grant. support/network reads and writes the frames and does no input or output
programs/netThe cases of the first layer of the network, with the answer of each, and the measures
programs/notes, support/storageA program that keeps a note and a count in its private store, and the client library of the storage service
programs/storageStorage service: the private store of each program on the data disk, through a block service. It starts at the first connection
programs/compositorCompositor and desktop shell: bar, clock, windows, focus, drag, close, pointer
programs/consoleConsole: integers, texts, variables, arithmetic, typed commands
programs/monitorSystem tool: memory, kernel entries, interrupts, pages and processor share of each program
programs/plasma, life, paintDemos: animation, simulation, pointer and key input
programs/templeDemo, for fun: a portrait of Jonathan Blow turns about its vertical axis, with perspective. The processor draws it
programs/faultA program that writes to an address that it does not own
programs/rings, programs/node, support/relay/Demo of communication between programs: six node programs in a circle, one ring from each node to the next. rings starts them, and draws the real content of each ring from the shared memory. A node has four memory objects and no other handle
contract/isolated/The isolated call: the layout of the argument record, encode for the caller, decode with the checks for the receiver
programs/hostThe call host: it loads one module and executes the isolated calls of its exports
modules/mathA module of commands: gcd, power, fibonacci, next_prime, number
modules/probeA module of commands that tests the sandbox: it reads any address, loops with no end, tries to stop the machine
modules/agenda, modules/calendar, calendar_fixed, calendar_changed, lonelyModules of commands for imports and replacements: agenda imports calendar; calendar has a fault, calendar_fixed corrects it, calendar_changed changes the description; the import of lonely is not in the boot image
programs/importsThe loader in its cases: a module with imports, each replacement, the refusals, and the measures. It imports calendar itself
contract/errors, programs/errorsThe error record of a call that failed, and the program that makes one error of each kind and shows the sentence of its record

Grants by the root service:

ProgramHandles
inputPorts and interrupts of the keyboard and mouse; a ring to the compositor
compositorThe framebuffer; the input ring; a control ring to the root service
consoleA display ring; a ring to the root service; the machine; the boot image; the replacement table, with Read
monitorA display ring; the machine
netA display ring; the machine, with Inspect; a datagram ring and a datagram region, with the grant of the remote 10.0.2.2 port 7
networkThe frame ring of its frame service; a control ring from the root service
ringsA display ring; the boot image, to start its node programs
all othersA display ring only

The console starts each call host. A call host receives a call ring and a call region only.

A program with only a display ring can draw in its surface and read its own input events. It can do nothing else.

Choices made during the implementation

The owner did not review these. Each one can change.

  1. 1. Export numbers. The kernel tool holds an ordered export list per module. The export index is the position in that list. Imports resolve by name at build time only.
  2. 2. Entry instruction. syscall, with the full register state saved per program and a return by iretq. The kernel is not preemptible and has one stack.
  3. 3. Give by memory. Handle_Give takes a memory handle to name "the other side of this ring". The kernel has no ring object: a ring is a memory object with a layout that the two programs agree on.
  4. 4. The kernel loads the root service only. The loader of all other programs is user-space code in support/system/load.odin. This closes open question 2 of ARCHITECTURE.md in the direction "minimal loader in the kernel, for one program".
  5. 5. Fixed addresses. A program module is at 0x40_0000. The stack, the context and the kernel ring have fixed addresses. No random base address.
  6. 6. The compositor is also the desktop shell. The glossary defines them as two programs. One program holds both roles now.
  7. 7. Grants by program name. The root service decides the handles of a program from its name, in code. Since step 25 it records the grants that it can revoke, and revoke ends one (choices 105 to 113).
  8. 8. Commands of two origins. The system commands (programs, start, shutdown...) are compiled into the console. load("math") loads a module of the boot image: each export becomes a command. The console reads the name, the types and the help of an export from the function table of the module, and calls the export with the call form. load checks the hash of the module first. The person chooses the trust. load gives direct calls: the module has the memory and the handles of the console. isolate gives isolated calls: the module runs in a call host. The description and the way to type a call are the same.
  9. 9. Font. Spleen 8x16, BSD 2-Clause, converted from its BDF file by kerneltool font. Since 2026-11-11 the default is a vector font and Spleen is the fallback (docs/FONTS.md).
  10. 10. Types of the description. Integer (64 bits, in a register) and Text (the address of a slice record). At most 6 parameters. For a Text result, the caller gives one more argument: the address of a buffer record {address, capacity}. The export writes what fits and gives the count of bytes of the full text. In an isolated call the buffer is the free part of the call region. The kernel tool makes the description of each export from the source (choice 26).
  11. 11. Isolated call. MODULE_CONTRACT.md section 4, "The isolated call, as implemented", lists these choices: one call host per module, the argument record in the call region, the status numbers, no time limit (choice 25), a call region of 16 pages, one call at a time per call host.
  12. 12. The caller starts the call host. The console creates the program with system.program_start; the root service takes no part. Any program can create a program: the new program has only the handles that its creator gives.
  13. 13. A program runs while a handle names it. The kernel stops a program when its last handle closes (stop code 0x200). Reason: nobody can stop a program that no handle names. A fault gives stop code 0x100 plus the exception number.
  14. 14. The context has no text. The 256 bytes of text of the context are removed: no program used them, and property 19 forbids them.
  15. 15. One kernel ring per thread. With one ring per program, a thread that the timer interrupts can get the completion of another thread. Each thread has a thread area that the kernel makes: the thread record (one page), then the kernel ring. THREAD_AREA_BASE is the address of the first one; a program has at most 32.
  16. 16. The gs register holds the address of the thread record. system.call must find the kernel ring of the thread that runs, on any stack. This amends rule 7 of the module contract: see there.
  17. 17. An event is for all the threads of a program. An interrupt, the end of a thread and the stop of a held program add 1 to the event word of each thread of the program concerned. A thread that waits on its event word wakes, reads the state, and waits again. The kernel keeps no list of who waits for what event.
  18. 18. A thread ends alone or by its handle. Thread_Stop(0) ends the caller. Thread_Stop(thread) ends another thread of the program at any instruction: the caller is responsible for the data that this thread was changing. Thread_State and the event at the end of a thread make system.thread_wait.
  19. 19. Order in the machine test. Two programs print in an order that the system does not fix. A Wait step with any_order does not move the place in the log.
  20. 20. The word wait. Property 6 proposed "a futex-like wait on a ring counter". Word_Wait is a command of the entry that waits: the kernel compares the word and records the wait in one step, so no wake is lost. A word wait ends only by Word_Wake on one of its words, by an event if the event word is in the list, or by the deadline. The word is 32 bits on all machines, the size of a ring counter.
  21. 21. The lock is a library. system.Lock is one word with three values (free, held, held with a thread that waits). The kernel has no lock object. A free lock costs one atomic instruction and no kernel entry.
  22. 22. The boot volume is a file that the tool writes. tools/kerneltool/volume.odin writes a FAT32 volume of 64 MiB with the boot program, and the machine gets it as a virtio block device. Before, QEMU made a FAT disk from a folder, on its SATA controller. With KVM, about 1 boot in 10 then stopped in the firmware, in a read of the disk, until an event came to QEMU (measured: 7 of 80 boots with a plain image on SATA, 0 of 160 on virtio). This was the intermittent failure of step 1 of the machine test. A test reads the volume back with a separate reader.
  23. 23. One wait. The word wait is the one way to wait for something else than a time. Wake(memory) and the wake-pending flag are deleted. A queue has two words more, reader_waits and writer_waits. The reader of a queue sets reader_waits, then waits on the write counter while the queue is empty. system.send writes a record, and enters the kernel to wake the reader only if reader_waits is set. system.receive does the same for a writer that waits for room. A system.Wait is a list of at most 64 words: the compositor waits on the queues of all its clients in one entry. Measured in the machine test: the picture of rings and the logs do not change.
  24. 24. The event word. The thread record has an events word. The kernel adds 1 to it for each event of the program (choice 17). A thread reads the word (system.wait_begin), reads the state, then waits on the word with the value that it read: an event between the read and the wait ends the wait at once. This replaces the wake-pending flag with the rule of the word wait.
  25. 25. The console runs a line in a second thread. The first thread reads the keys and draws. The second thread runs each line. The two threads share one Work record and wait on its words. During a line, the first thread keeps at most 128 keys, shows the time of the line in a status line, and sets the cancel word on Escape. An isolated call waits on the completion queue and on the cancel word in one entry. The limit of 2000 ms is deleted: the person decides. Escape does not stop a direct call or a command of the console: that code runs in the console itself, and choice 18 says that a thread stopped from outside can hold a lock.
  26. 26. Descriptions come from the source. tools/kerneltool/describe.odin reads each Odin package with core:odin/parser. An export is a procedure with @(export) and the calling convention "c". i64 is an Integer, ^string is a Text, and a last parameter ^[]u8 is the buffer of a Text result. The description holds the name of the export, the name and the type of each parameter, the result, and the first sentence of the comment as the help. Another type, a missing comment or an export of data is a build error: the tool does not guess. The module table has no description. A function record is 56 bytes. The test reads each module file and compares its descriptions with the source. The kernel exports functions only: its two words that the assembly reads are now in processor.asm. The rules are those of the Odin binding; the description in the module file does not depend on Odin.
  27. 27. Rights on handles. A right is one bit of a 32-bit set in the handle slot. There are 8 rights for 6 kinds of object; the table in contract/operation/operation.odin says what each right permits for each kind. Memory_Map needs Read, and Write or Execute when the flags ask for them. An operation on another program needs Control on its handle. Handle_Give needs Give on the given handle, and Control when the target is a program handle. The copy has the intersection of the rights of the handle and of the mask: a grant never adds a right. system.program_start takes grants: a handle and its rights. Each caller names the rights; there is no default "all rights". The boot image handle of the root service has no Write and no Execute. A surface goes to the compositor with Read only. The Machine handle is not split in three kinds: one kind with three rights uses the one mechanism. A thread handle can go to another program with Read only: that program reads the state and cannot stop the thread.
  28. 28. Budgets: no operation creates an object from nothing. Property 20, line 16, says: "each resource is a handle with a limit; a program divides its limit among its children". A budget is that handle. Three other designs were refused. A limit per program that is only a ceiling does not conserve: the children of a program multiply it. A count per program with no object cannot be given or divided by the handle rules. A budget as a field of the program record makes a second mechanism beside handles. With a budget object, the rights apply (Give, Read, Spend), and the end of a program returns all: system.program_start makes a new budget for the program, pays for all that it creates with it, gives the one handle to the program, and closes its own. Two indexes of the context are fixed by the system for each program: CONTEXT_LOG (14) and CONTEXT_BUDGET (15); system.log and system.memory_create read them there, so no program has code before its entry. The page tables of a program are paid by the budget that paid for the program. The kernel has no pool of its own after boot: free pages = sum of the budgets is the invariant, and the test reads it. The root service chooses each budget by name (budget_of).
  29. 29. The log is an object. The log of the kernel was the last operation that any program could use. It is now a handle with the right Write. The root service gives it with Write; the console and rings also receive Give, because they start programs. A program with no log handle logs nothing: system.log returns.
  30. 30. Several processors, one kernel lock. A processor takes the lock at each kernel entry and frees it when it returns to a program or waits for an interrupt. Interrupts are off in the kernel. Property 3 puts the kernel off the hot path: a program enters the kernel to wait, not to work. So the lock is held for a short time, and finer locks add states that nothing needs yet. The kernel counts each acquisition that had to wait (lock_waits in System_Report), so the cost is a number that memory() prints. In one run of the test: 1.7 million kernel entries, 0.6 million waits, most of them during rings, where six programs do nothing but wake each other. Review 2 planned one run queue per processor. It is not done: the thread table is flat and the lock covers it, so a queue per processor adds a second structure with no gain while there is one lock.
  31. 31. The processor record. In the kernel, the gs register holds the address of the record of the processor; in a program, it holds the address of the thread record. swapgs exchanges the two at each entry and each return. The kernel has no global "current thread". Each processor has its own kernel stack, because a kernel entry starts at the top of the stack and never continues an earlier entry.
  32. 32. processor_sync. The caller holds the kernel lock. It sends an interrupt to a set of processors and waits until each one is in the kernel. Such a processor reloads its page table, then waits for the lock. Two uses. After Memory_Unmap and at the stop of a program, no other processor keeps an address translation to memory that returns to the page frames. When a thread on another processor is stopped, the kernel writes 0 in current of that processor, then waits: after the answer, that processor does not use the thread, and its entry is not an entry of a thread.
  33. 33. Start of a processor. The boot program gives the address of the ACPI root record in Boot_Info. The kernel reads the processor list (MADT). It copies the start code, less than one page, to the page 0x8000 and starts one processor at a time (INIT, then SIPI). The start code goes from 16 bits to 64 bits with a temporary page table. If the machine has no ACPI tables, or no free low page, the kernel runs with one processor and says it in the log.
  34. 34. The timer is the local APIC. The kernel measures it against the 8254 once. An idle processor stops its timer, except the first processor. The device lines stay on the 8259: the system has two device interrupts (keyboard, mouse), and a new device class will send its interrupts as messages to a local APIC (MSI), so the kernel needs no I/O APIC code.
  35. 35. One table for the launcher. programs/launcher holds the names and the place of each button. The compositor draws from it and the kernel tool clicks from it: the test step is Launch with a name, not a click at a number.
  36. 36. A failure of the check that came sometimes: found. The flake check of step 11 failed once with "runtime assertion: import" in a unit test of the tool. The tokenizer of the Odin library (core:odin/tokenizer) fills its table of keywords at its first use, and its test of "already filled" is outside its lock. The test runner gives each test a thread: two tests that parse Odin source at the same time fill the table twice, and the second stops on an assertion. The tool itself has one thread and is not affected. tools/kerneltool/describe_test.odin now fills the table before the threads start.
  37. 37. The boot display is one picture of the records of the kernel. Owner decision of 2026-11-10: the start of the machine must show a rich, coloured and documented display, with all the facts that help to find a fault, and not a terminal of an old machine. support/bootscreen/ draws one record (bootscreen.Data) at the full resolution of the framebuffer. Each panel names the kernel record that it shows. A record that is all zero is valid: its panel says "not known yet". Three programs draw it: the boot program, the root service, and the program boot in a window. The kernel does not draw.
  38. 38. The boot record. Inspect has a new kind, Boot, with the record Boot_Report: the stages with their times, the memory ranges of the firmware, the firmware, the framebuffer, the sizes, the frequencies of the cycle counter and of the timer. The kernel reads the cycle counter of the processor at the end of each stage. The counter is 0 when the machine starts, so the time before the boot program is the stage "firmware". The kernel measures the frequency of the counter with the wait that already measures the timer. Program_Report has start_time, and Processor_Report has apic_id. The kernel also writes each stage on the log ("kernel: boot: ...").
  39. 39. A start that fails keeps the display. Before, the root service stopped the machine when a start failed. Now it stops the compositor if it runs, draws the boot display with the failure in red, and waits. A person reads the display and stops the machine. The kernel tool builds a second boot image with no compositor, and a second boot volume, and the machine test checks this path (scenario_fails).
  40. 40. No pause for the eye. With a start that succeeds, the boot display is on the screen for about 20 ms under QEMU: the system does not wait so that a person can read it. The program boot shows the same display at any time.
  41. 41. The launcher and large windows. The buttons of the launcher have 4 pixels of padding and 2 of gap (6 and 4 before), for the names (15 now). The compositor moves a new window up and left until the screen shows all of it.
  42. 42. Paging protects the kernel from itself. Owner decision of 2026-11-10: paging is a design tool. kernel/paging.odin gives the kernel half of the addresses three parts. The direct map is data: no address of it is executable. The kernel module has each segment with its own protection, and its code and constants are read-only in the direct map too. The kernel stacks are in their own region. No page of the kernel or of a program is both writable and executable: Memory_Map refuses the two flags together.
  43. 43. Guard pages around each kernel stack. Each processor has a slot of 32 pages of addresses: a guard page, the kernel stack of 16 pages, a guard page, the stack of the double fault of 4 pages, and guard pages to the end. A guard page has no memory. A stack that is full gives a page fault, then a double fault on its own stack, and the kernel reports "the kernel stack of the processor is full". Before, the stack was in the processor record, and a full stack wrote on the record. The first processor leaves the stack of the boot program before it runs the first thread.
  44. 44. The kernel uses the memory of a program in one place. user_copy (in kernel/processor.asm) is the one place where the kernel reads or writes at the addresses of a program. If the processor has the guards (SMEP and SMAP), the kernel sets them: any other use, and any jump to the code of a program, is a page fault. The kernel copies the parameters of an operation to its own memory before it uses them. The QEMU machine has -cpu max so that the test has the guards.
  45. 45. The kernel reads its own tables. Inspect has a new kind, Mappings, with the record Mapping_Report: the runs of pages with one protection, the counts, the guard pages. At the start, the kernel reads its half and stops the machine if a page is writable and executable or if a guard page is missing. The program pages shows the same record.
  46. 46. Tests of the kernel on itself. A test that the kernel survives proves nothing about a protection: the test must do the thing that is refused. The boot record has a field self_test. The kernel tool builds one boot program for each test (-define:SELF_TEST=), and the machine test checks the report of each fault. A normal boot program has the value 0.
  47. 47. The launcher is a menu. The bar had one button for each program, and 15 names filled it. The bar now has one button, "programs". It opens a menu with one line for each program. A press on a line starts the program, and any press closes the menu. programs/launcher still holds the one table of the names and the places: the compositor draws from it, and the machine test clicks from it. The buttons of choices 35 and 41 do not exist any more.
  48. 48. The log is records in channels. Owner decision of 2026-11-10. A log record (Log_Record, 256 bytes) has a number, a time, a program, a thread, a level, a channel and a text. A long text takes several records. The operation Log takes a level and a channel; system.log_in(channel, level, ...) writes one, and system.log(...) writes with no channel and the level Info. The serial port receives the same record as one line: "[program] text", or "[program:channel] text".
  49. 49. The kernel is the one writer of the log. The first design (in HANDOFF.md) had a log service in user space. The implementation has none. The kernel must set the program and the thread of a record, or a program could write a record of another program. With one writer, the log is one memory object with a counter: the last 1023 records. A reader maps it with the right Read and waits on the counter with the word wait. The kernel does no work for a reader: no copy, no operation. A reader that is late knows how many records it lost. A service that keeps or filters records can be a reader later.
  50. 50. Who reads the log. The root service receives the memory object of the log with Read (ROOT_LOG_RECORDS) and gives it to the program log (GRANT_LOG_RECORDS). No other program has it. The kernel writes its stages in the channel "boot", its page tables in "paging", and a program that stops by a fault in "fault" with the level Warning. The root service writes in "programs", the compositor in "surfaces".
  51. 51. No standard input and no standard output. Owner decision of 2026-11-10. A program has no ambient stream of bytes: its context has no place "input", "output" or "error". The input of a command is its typed parameters and its output is its typed result (property 19). A flow of data in time is a ring of records. The log is not an output: it is for the person who looks for a fault.
  52. 52. A program that runs serves its own exports. The file of a program is a module: its exports have descriptions, made from the source as for each module. A service reads the descriptions in its own file (the root service maps the file in the program, read-only, at isolated.FILE_ADDRESS) and decodes each argument record with them. The caller reads the same descriptions in the same file. The isolated call has one form for a call host and for a service: system.export_serve executes it in the two cases. No contract is specific to services, and no language feature is in the contract: a command record, an argument record and a description.
  53. 53. The root service makes each connection. A caller asks the root service by the name of the service (Root_Command.Connect). The root service creates the call ring and the call region, gives them to the two programs, and keeps none. The service learns of the connection on its service ring (isolated.Service_Command). The caller receives a handle to the program with the right Read only: it can learn that the service stopped, and it cannot stop it. The caller does not hold a name or an address of the service: it holds handles (P20). Today the root service gives the grant to serve to paint and life, and lets each program with a ring to the root service connect; a list of who can call which service is part of the step on principals.
  54. 54. One connection for each caller. A caller cannot read or write the arguments of another caller: each connection has its own call region. A service has at most 8 callers (SERVICE_CONNECTION_CAPACITY). The service ring always keeps room for one Disconnect for each connection: the root service never waits for a service that does not read its ring. A service that gives false counts on its ring harms only itself.
  55. 55. The calls execute in a thread that the service chooses. system.service_wait adds the service ring and each call ring to the wait list of the thread, with the other things that the program waits for. system.service_run executes the calls. In paint and life this is the one thread of the program, between two input events: an export reads and changes the data of the program with no lock. A service that needs parallel calls gives each connection to a thread of its own.
  56. 56. The caller names the file by its hash. The completion of Connect has the hash of the file of the service. The console finds the file in the boot image by this hash and checks the hash before it reads the descriptions. Each export becomes the command service.export. When the service stops, the root service ends the connection, and the next call asks for a new one. If the new program has another file, the console refuses the call: its commands describe the first file.
  57. 57. A ring has a capacity, chosen when it is created. The memory of a ring is one head page (the counters of the two queues), then the command records, then the completion records: 1 + capacity / 32 pages. The capacity is a power of two from 32 to 65 536. The smallest ring has 2 pages, as before. The capacity is not written in the shared memory: each side computes it from the size of the memory object, that the kernel gives (system.ring_open). A side cannot make the other side read outside the ring. A ring does not grow: a queue that grows moves the problem of a slow reader into memory, and needs the kernel on the hot path to map it again in two programs.
  58. 58. A full queue slows the writer to the speed of the reader. Owner decision of 2026-11-10. The capacity is for the bursts of the writer. Each writer chooses what it does when the queue is full: it waits for room (wait_room), it drops the record, or it refuses the request. The writer and the reader change different lines of the processor cache (ring.Counters).
  59. 59. One wake for many records. A writer that waits for room continues when half of the queue is free, or when the reader itself starts to wait: it then writes many records for one kernel entry. wait_end sets the waits word of a queue, not wait_records: a thread that does not wait does not make the other side enter the kernel.
  60. 60. Read the counters before a wait. system.wait_spin reads the words of a wait list about 400 times with no kernel entry, on a machine with more than one processor (the thread record has the count of processors). If a word changes, the thread does not wait, and the other side does not enter the kernel to wake it. A program calls it where the other side answers fast. wait_end does not do it alone: it gives the time, and a program reads the time with a kernel entry today. The program flow measures the effect: 2 500 times fewer kernel entries with capacity 32.
  61. 61. A terminal is a program, and its input is records. The package terminal is the whole contract: a terminal ring of 4 096 records and a text region of 4 MiB. A record has a kind (Text, Status, Clear), a count of bytes and a style (a tone, flags, a color). No byte of the text has a meaning for the terminal: no escape character, no escape sequence, no control character. The end of a line is a flag of a record. A progress line is a Status record with a value and a total: the terminal shows it out of the lines. The kernel knows nothing of this: the two grants are a ring and a memory object.
  62. 62. A record gives a count of bytes, never a place. The program writes its bytes in the text region, in order, and the record says how many. The terminal keeps its own read place. A program cannot make the terminal read memory that it chooses. The terminal maps the text region read-only, limits each run to 256 bytes, and copies the bytes one time, from the text region to the bytes that it keeps: after that copy, the program cannot change what the terminal shows.
  63. 63. The display does not slow the program. One thread of the terminal reads the records and keeps the lines. It never draws. The first thread draws at the rate of the screen, and it draws only the cells of the view: 32 rows of 104 cells. It asks the reader for the view with one word; the reader answers between two groups of records. The data has one owner and no lock. The cost of the display does not depend on the count of lines written.
  64. 64. The terminal keeps a fixed amount. Three areas of a fixed size: 65 536 lines, 262 144 runs, 8 MiB of bytes. The oldest lines leave. Nothing grows while the program runs. When the person holds a view and its lines leave, the view keeps its cells and the window says "older than the lines kept".
  65. 65. One wake for a group of runs. terminal.line writes its runs with ring.push and calls system.wake_reader one time. ring.push and ring.pop store the counter with no complete barrier: it costs more than the store. A side calls ring.complete before it reads the waits word of the other side.
  66. 66. A waits word is a word of the wait. Step 17 had a lost wake: flow stopped in 2 test runs of 3. The side that wakes sets the waits word of the other side to 0. A late wake, for a record that the thread already read, could set the word of a newer wait to 0: the next record then made no wake. Now wait_end gives each waits word to the kernel as one more word of the wait, with the value 1. If the other side set it to 0, the kernel does not stop the thread. wait_end also wakes the other side of each queue before the thread stops: a reader that was not told of the last records, or a writer that waits for half of the queue.
  67. 67. A surface can have two pictures. The first picture of the held terminal showed lines of two different views: the compositor read the surface while the terminal drew it. display.open with two_pictures creates memory for two pictures. Present names the picture that is complete; the program draws the other one, after the compositor took the last Present (display.canvas_free). The compositor checks the size of the memory before it accepts a picture number. A program that draws small changes keeps one picture.
  68. 68. A film is a command of the tool. kerneltool film starts the machine, starts the terminal, sends keys, and asks QEMU for the screen 25 times in one second. ffmpeg, from the flake, makes the file. The film is for the development log: it is not a test.
  69. 69. Measured. In the test machine (4 processors, QEMU with KVM, the other programs of the scenario open): over five runs, flood writes 1.7 to 3.0 million lines in one second, 6.8 to 11.9 million records, with 14 to 108 kernel entries for one million records, and the terminal draws 47 to 50 pictures in one second. In the machine of the film (the terminal alone): 3.6 million lines, 14.5 million records, 1 to 6 kernel entries for one million records, 62 pictures in one second.
  70. 70. A shared queue: several writers and several readers, no lock. contract/ring/shared.odin. A ring stays the way between two sides: one writer, one reader, no atomic exchange. A shared queue is for the other case: several threads take work from one place, or give results to one place. Each record has a turn word. A writer takes a count with one compare-and-exchange on write, writes the record, then changes the turn word; a reader does the same with read (the algorithm of Dmitry Vyukov for a bounded queue). The turn word holds the count minus the place of the record: memory that is all zero is an empty queue (P16). The capacity comes from the size of the memory, as for a ring. A record is the same 64 bytes as in a ring.
  71. 71. A wake for one thread, and three rules that keep the kernel off the path. A writer adds 1 to the word written after each record, and a reader waits on that word. system.wake_one wakes one reader only if a reader waits, if no reader reads the word in wait_spin, and if the reader of the last wake already runs. A reader that takes a record and sees one more wakes the next reader: the readers that run are as many as the work needs. Without the third rule, a reader that sleeps costs many kernel entries: measured in the test machine, 82 370 and 346 099 kernel entries for one million records with 2 and 3 readers; with the three rules, 6 778 to 13 813 over two runs.
  72. 72. Writers wait for half of the queue. A writer that finds no room sets writer_waits and waits on the word taken. A reader wakes all the writers when half of the queue is free: one kernel entry, then many records. This is choice 58 for a shared queue.
  73. 73. A flag that several threads set is not cleared by a thread that leaves the wait. In a ring, one thread owns each waits word, and wait_end sets it to 0. For an event and for writer_waits, another thread can still wait: the thread that wakes sets the flag to 0 with an exchange, before the wake. A thread that waits gives the flag to the kernel as a word of the wait, as in choice 66: if the flag changes first, the thread does not stop.
  74. 74. An event is two words. system.Event has a count and a flag. event_wake adds 1 and wakes all the threads that wait; wait_event adds the event to the list of a wait, with the queues and the other words. The zero value is an event that never happened. The condition is in the memory of the program, not in the event: a thread reads the count, reads the condition, then waits while the count is the same. crowd starts the threads of each measure with one event, and its queue with a lock uses two events.
  75. 75. The threads of a shared queue trust each other. A thread that writes wrong numbers cannot make another thread use memory out of the queue: each place is a count and the mask. It can make the other threads lose records. A thread that stops between its compare-and-exchange and its turn word stops the queue at that record. Between programs that do not trust each other, the way is a ring for each pair.
  76. 76. The scenario reads the place of a window in the log. The compositor writes "surface name WxH at X,Y". The step Move of the scenario reads that line and drags the title bar from that place. Before, each drag had fixed coordinates, and a new window in the scenario moved the windows after it: the scenario failed for a reason that was not in the system.
  77. 77. Measured. In the machine with crowd alone (4 processors, QEMU with KVM): 33.9 million records in one second for 1 writer and 1 reader, 12.4 million for 2 and 2, 12.8 million for 3 and 1, 11.3 million for 1 and 3, each with 2 to 6 kernel entries for one million records. The queue with one lock and two events, 2 and 2: 11.6 million records, 157 009 kernel entries for one million records. In the test machine, with the other programs of the scenario open, two runs: 32.2 to 32.6 million (1 and 1), 14.9 to 15.6 million (2 and 2), 22.8 to 23.4 million (3 and 1), 13.2 to 14.6 million (1 and 3), with 29 to 13 813 kernel entries; the queue with a lock: 8.4 to 10.2 million with 151 488 to 152 591. More threads on one shared queue give fewer records in one second than 1 writer and 1 reader: the threads share the lines of the cache of write and read. With 2 writers and 2 readers, the shared queue gives 1.1 to 1.8 times the records of the queue with a lock, and enters the kernel 11 to 78 000 times less. More records in one second need more queues, not more threads on one queue (owner decision of step 17).
  78. 78. The time is a page, not an operation. The kernel has one page, the time page. program_create maps it in each program at TIME_ADDRESS, read-only and not executable. The record Time_Record has one field, the milliseconds since boot; the first processor stores it at each tick of its timer. system.time() is one load of 64 bits: the value is always complete, and no sequence count is necessary. The contract is an address and a record: a program in any language reads it. The time was already free to each program (P20): the page gives no new knowledge. The record also has the cycles of the cycle counter in one second (choice 208).
  79. 79. No budget pays for the time page, and no program removes it. The page is memory of the kernel, taken before the first budget exists: the check "budgets N pages: all the free pages" still holds. Memory_Unmap refuses each address at or above the thread areas, and the time page is above them. A second program cannot map another object at this address in a new program: the address is taken at creation.
  80. 80. The kernel entry that gives the time stays. system_enter still returns the time: a thread that leaves a wait has the time with no second read. system.time_entered names that path for the measure.
  81. 81. Measured. In the test machine, four runs: clock reads the time 879 to 1 491 million times in one second from the time page, with 0 kernel entries, and 1.9 to 2.4 million times in one second with a kernel entry: 460 to 620 times more. A program that reads the time in a loop (each program that draws, each measure) no longer takes the kernel lock for it.
  82. 82. One module in each role (owner, 2026-11-10). The system has no difference between a command line program, a library and a dynamic library: the file of tally is a program (it has an entry), a service (the program that runs serves its exports), a library (the console maps it in its own memory and calls it directly) and a sandboxed program (a call host runs it). The four uses name one hash. The data of a module belongs to the program that runs it: the four uses give three counts. One rule makes a module good for each role: an export needs nothing from the entry.
  83. 83. A module carries its documentation. The file has a Documentation table: record 1 has one line that says what the module is, and a text. A record of the Functions table also has the text of its export. The tool takes the text of the module from the comment before the word package, and the text of an export from its comment. A package with no such comment does not build. The text has one form (module.Documentation): a line is a paragraph, a line that starts with a space stays as it is. The reader breaks the paragraphs: the file does not fix a width. The documentation is in the hashed file: it cannot describe another version of the code.
  84. 84. The manual reads the file and nothing more. manual shows the page of each module of the boot image: it does not load the module and does not start it. The ways to use a module come from two facts of the file: an entry or not, exports or not. The manual is a service with three exports: show asks for a page, about and signature give a description as a text. The console and the manual read the same records: a module has one description, and each program that wants it reads it.
  85. 85. The console removes a module. unload removes the commands of a module that load or isolate added. A direct load leaves the memory of the console (system.module_unload removes each segment). A call host stops when the console closes its handle. A sandbox of the console that has no name is free: the console has 8, and it uses a free one again.
  86. 86. The compositor writes the place of a window that a person puts down: "compositor: window title moved to X,Y". The step Move of the scenario reads the last place of a window, and it fails when the window is not at the new place after the drag. Before this, a second Move of one window used the first place, and the test did not see it.
  87. 87. Two source libraries, and no relative import (owner, 2026-11-10). The packages that several modules compile in are in two folders, and an import names the library: contract:ring, support:system. contract has what two parties agree on: the kernel operations, the boot record, the module format, the hash, the ring, the isolated call. It uses no kernel operation. support has what a program compiles in: system, graphics, display, terminal, bootscreen, launcher, relay. The limit is checked by the compiler: the kernel and each package of contract compile with the contract library only, so an import of support: there is a build error (6 checks). The flake defines the command kerneltool with the two libraries.
  88. 88. The root service wires a program and its terminal; the terminal starts nothing. In step 18 the terminal started flood: it held the boot image and a part of the Machine handle, and each program in a terminal had to be a child of the terminal. Now a role of the root service says "this program has a terminal" (Terminal). The root service creates the terminal ring and the text region, starts the program with its role, then starts a terminal for it. The terminal receives a surface, the terminal ring, the text region with the right Read, and a handle on the program with the right Read. It has no boot image, no Machine handle, and nothing of the role of the program: the console can stop programs, its terminal cannot. The name of the program, the size of the text and what the window shows are values of the context of the terminal.
  89. 89. Input is records in the completion queue of the terminal ring. One record for each key that the person presses: the code of the key and its character. No byte stream, no mode "raw" or "cooked", no signal character. The terminal keeps Page Up and Page Down for its view. Each other key goes to the program, and the view returns to the last lines. The terminal never waits for a program: if the program does not read and the queue is full, the key is lost and the log says it one time.
  90. 90. The program owns its line of input; the terminal draws it (the kind Edit). The program sends the text of the line that it changes and the place of the cursor. The terminal shows it after the last line, and a new one replaces the one before. So no record has a place on the screen, the terminal has no line editor and no history, and two programs can edit a line in two ways. The console keeps its history and its keys. While a line runs, the console uses the same record to show the time of the line and the keys that wait.
  91. 91. A program in a terminal has no surface. The console lost its window, its grid of cells and its drawing code: programs/console/main.odin went from 329 lines to 256. It writes runs with a tone (an error has the tone Bad) and the terminal chooses the colors. Its two threads write one ring: a lock orders them, because a queue has one writer.
  92. 92. A terminal draws after a change only. The reader thread counts the groups of records. The drawer of a terminal that does not show rates waits on that count, on its keys, and looks at the state of its program each 250 ms. A console that waits for the person draws no picture. The terminal of flood shows rates: it draws at the rate of the screen.
  93. 93. A program that stops leaves its terminal. The terminal writes "-- name stopped with code N after N lines --" and keeps the lines: the person can read what a program wrote before a fault. The person closes the window to stop the terminal. A terminal that stops (the person closes its window) stops its program: a program with no terminal can show nothing.
  94. 94. Program_State gives the count of kernel entries of the program. The terminal showed this count with Machine_Report, which needs the right Inspect on the Machine handle and gives the facts of all programs. The count is now a third result of Program_State, which needs the right Read on one program. The terminal has no Machine handle.
  95. 95. The scenario closes a window with its close button (step kind Close). The tool reads the place and the width of the window in the log and presses the button. Before this step no test pressed a close button.
  96. 96. A Record is bytes with described fields (module.Type.Record). The description of a module has two more tables, Records and Fields: the name and the size of each record, and the name, the offset and the type of each field. A field is an Integer (8 bytes) or a Handle (4 bytes). A record has at most 256 bytes and holds values only: no address. In a direct call the register has the address of the record. In an isolated call the bytes go in the call region, and the receiver copies them to private memory before the call: the caller cannot change a record while the export reads it.
  97. 97. A Record result is one more argument: the address of the memory of the record, after the parameters. The export has no result type. A result record has no Handle: a callee gives no handle back in this step.
  98. 98. A Handle is lent (module.Type.Handle). The description has the rights that the export needs. In a direct call the register has the handle of the caller. In an isolated call the caller gives a copy to the receiver with these rights only (system.lend), and the receiver closes the copy when the export returns (system.export_serve). The caller keeps its handle. Measured in the test: an export with a description of Read receives No_Right when it maps the object to write, and a handle that an export kept gives Bad_Handle at the next call.
  99. 99. The kernel proves where a handle came from (the operation Handle_State, number 23). A receiver reads the number of a lent handle in memory that the caller writes. Without a proof, a caller names a handle that the receiver holds for itself, and the export uses it: a confused deputy. Handle_Give with a memory object as the target now records the serial number of that memory object in the new handle. Handle_State(handle, memory) says if the handle came through this memory object. The receiver calls the export only if each handle came through the call ring, with each right of the description. A memory object has a serial number because a slot of the table of memory objects serves many objects in time.
  100. 100. The caller checks before it gives. system.lend reads the rights of each handle first: the Give right and the rights of the description. If one handle fails after others went, the caller sends a record of the kind COMMAND_RELEASE with the handles that the receiver has, the receiver closes them, and the caller does not send the call. At most 16 handles go with one call.
  101. 101. The source gives the descriptions. In Odin, a parameter ^Name is a Record when Name is a struct of the package with the layout of the C language; a last parameter result: ^Name is a Record result; system.Lent({.Read, .Write}) is a Handle with its rights, as a parameter or as a field. Lent is a struct of 4 bytes: the C convention passes it in a register as the handle. The tool computes the offsets, and the test compares each record of each module file with the source. A contract does not depend on Odin: a module in another language writes the same tables.
  102. 102. A record has no identity but its description. The console accepts a Record argument when the two descriptions are equal: the same name, size and fields (module.records_equal). A record of one module is a record of another module with the same fields. The replacement of an import will use the same comparison.
  103. 103. The console has Record and Handle values. Each record of a loaded module is a command with the name of the record: Rectangle(1, 2, 30, 4) makes a value from one argument for each field. variable.field reads a field. The console prints a record with the names of its fields, and a handle with its object and its rights, which it reads from the kernel. buffer(pages) makes a memory object, byte(handle, offset) reads one byte of it, release(handle) closes it. help() and manual show "Handle(Read, Write)" and the name of a record in a signature; manual lists the records of a module with their fields.
  104. 104. Handle_Give to a memory object that one other program holds. Before, the target was the other program that maps the memory object. If no other program maps it yet, the target is now the one other program that holds a handle to it. With two other holders the kernel refuses.
  105. 105. The kernel keeps the origin of each holding (step 25, kernel/origin.odin). A holding is a handle or a mapping. One flat table has one link record of 8 bytes for each holding of each program: 64 programs x 256 holdings, 128 KiB, no allocation. A holding is in the list of the holdings of its origin, with a link in each direction: attach and detach are a constant number of writes.
  106. 106. A close does not hide what a program passed on. When a handle closes, or a mapping is removed, what came from it comes from its own origin. A program that gives a handle and closes its own does not cut the chain: the first giver can still revoke. Many programs map a memory object and close the handle: the mapping then has the origin of that handle, or none.
  107. 107. The operation Handle_Revoke, number 24. (handle, program or 0) ends each holding that came from the handle, in one program (a Program handle with the right Control) or in each program, and what came from those holdings in any program. The handle stays. The mappings of the program that revokes stay: a program does not remove its own memory when it takes back what it gave. The walk has no recursion: the kernel stack does not grow with the length of a chain. The result is the count of holdings that ended.
  108. 108. A program that lost a holding learns it or stops. Each such program gets an event. A thread that waits on a word of memory that its program lost wakes. A thread that uses the memory stops its program on a page fault: the kernel has no other honest answer to a read of memory that is gone.
  109. 109. Handle_Give with the target 0 makes a copy in the same program, with a part of the rights. It needs no Give right: no other program receives a handle. The copy is the unit of revocation: a program makes one copy for each party that it gives to, and revokes that copy alone. Handle_State has one more result: the count of the holdings that came from the handle.
  110. 110. A lent handle is revoked, not trusted. system.lend makes a copy for each handle of a call, gives the receiver a handle that comes from the copy, and system.lend_end revokes and closes each copy after the call, on each path. Before, the receiver closed its handle, and a mapping that an export kept stayed. The record kind COMMAND_RELEASE of contract/isolated is removed: the caller takes back what it gave when it does not send the call.
  111. 111. Measured (program origin, 20 000 cycles, QEMU with KVM, in the test machine with 20 programs open): one lent handle costs 4 kernel entries for the caller and 2000 ns: the copy, the handle of the receiver, the revocation, the close. A copy and its close cost 2 kernel entries and 1100 ns. Before step 25 a lent handle cost the caller 2 kernel entries (the state, the give); it now costs 5 (the state, the copy, the give, the revocation, the close). The cost is for each Handle argument only: a call with no handle has the cost of before.
  112. 112. The root service records the grants that it can revoke. For each program: the log, the machine, the boot image, the records of the log. Each one is a handle that the root service keeps, and the handle of the program came from it: Handle_Revoke(handle, program). Root_Command.Grants reads the two sets (has, lost) and Root_Command.Revoke ends one grant. The console has grants("name") and revoke("name", "grant"). A ring is not in the list: the root service closes its handle of a ring after the start, and the end of a ring is the stop of one of the two programs.
  113. 113. The revocation of the boot image also removes the mapping of the file of a service. The root service maps the file of a program from the boot image handle. A service that reads its own descriptions after such a revocation stops on a page fault. The root service revokes only what the person asks.
  114. 114. Found by this step: the compositor wrote past its table of windows. order had 16 places and the removal of a window copied from the place after the last one. With 17 programs in the scenario the compositor stopped on a bounds check. The table has 32 places (the root service starts 32 programs at most) and the copy uses slices.
  115. 115. handle_close of a Ports handle closes the ports at once on each processor that has the ports of the program open. Before, the ports stayed open until the next change of program on that processor: a revoked Ports handle must not leave a port open.
  116. 116. A budget has two limits: processor time and log records (step 26, kernel/limit.odin). Pages, programs and threads are things: they move to a new budget and they return. Time does not return. So processor is microseconds of processor time in each second, on all the processors together (1 000 000 is one processor), and log_records is records in each second. Budget_Amounts has 5 fields; Budget_Create and Budget_Read carry them. No kernel operation was added.
  117. 117. A limit counts in each budget above. What a thread uses counts in its budget and in each budget that this budget came from. A new budget cannot have a limit above the limit of its origin budget. The first design moved processor time like pages: then the sum of the limits of all the programs could not exceed the processors of the machine, and 30 programs that mostly wait took the processor time of the programs that compute. A limit that counts upward has no such cost, and a program still cannot give more than it has.
  118. 118. The kernel counts processor time with the cycle counter, from the moment when a processor takes a thread to the moment when it leaves it. The time of the kernel entries of a thread, and of its wait for the kernel lock, is time of the thread. The first idea was the count of timer ticks that programs() shows: a thread that waits just before each tick is never counted. That is a hole, so it was not built.
  119. 119. The limit holds in each period of 100 ms. A thread of a budget that used its part does not run until the next period: the scheduler passes over it, and the timer tick ends its time slice. What a budget used above its part (at most one tick for each processor) it has less in the next period: over many periods the limit is exact. Measured by limits: 49 642 of 50 000, 198 746 of 200 000, 602 592 of 600 000 microseconds in one second.
  120. 120. A period has its true length. The first version counted a period as 100 ticks of 1 ms. Under QEMU a tick can come late; then the threads of a busy budget used more cycles than "100 ms", the budget carried a debt that grew, and the budget of the root service (all the programs) stopped the whole system: the machine test failed two times in three (a program that started did not run). The kernel now measures each period with the cycle counter, and a budget has its part of the true length. Three full runs passed after the change.
  121. 121. Limit 0 is no time. A budget that is all zero has no processor time and no log record: its threads do not run, and Log gives No_Budget. Each place that makes a budget names the limits: the root service gives each program one processor for each thread that it can have and 1000 log records in each second; the console gives a call host half a processor and 100 records; rings gives each node 20 % and 100 records.
  122. 122. The log limit: the kernel refuses, and counts. Log gives the status No_Budget above the limit, and Budget_Read gives the count of refused records. The budget of the root service has the limit of the capacity of the log in records: all the programs together cannot write more than one full log in one second.
  123. 123. thread_start takes a budget. The kernel operation Thread_Create always took the budget that pays; the library now gives it. A program limits one of its own threads with a budget of its own.
  124. 124. The scenario clicked the console at one fixed place, and a new program moved the windows over it (the place of a new window depends on the count of windows). Two Move steps repair the scenario of this step. The right repair is a way to bring a window to the front by its name; it is the next step.
  125. 125. The bar has a menu of the windows (step 27). The button windows opens it: one line for each window, with its title, in the order of the client table of the compositor (a line does not move when the focus changes). A press on a line brings the window to the front and gives it the focus. A window that other windows cover fully had no way back before: the person can only press what the screen shows. support/launcher holds the place of the button and of each line, for the compositor and for the tool.
  126. 126. The compositor writes the lines of the menu on the log when the menu opens ("compositor: windows: console, life, monitor") and writes "compositor: window console in front" after the press. The step kind Front of the machine test opens the menu, reads the line of the title in the log, presses it, and waits for the second text. The 14 clicks at the fixed place (70, 300) are Front steps now: a new program in the scenario cannot move a window over the place of a click. The two Move steps of choice 124: the one of processors is removed; the one of flood stays, because fixed clicks in its window follow.
  127. 127. The tool reads only complete lines of the log. QEMU writes the log file one byte at a time. A Wait on "compositor: surface paint" can pass when the file ends in the middle of that line, and the Move that follows then reads a part of "at 168,142" as another place: the pointer does not take the title bar, and the test fails with "the window paint is not at 516,408". Measured on one full run: 17 of 2905 reads of the file (one each 20 ms) ended with a part of a line. The two Move steps of paint are the only ones that directly follow a wait on their own surface line: this agrees with the one failure of step 24, at that step. log_read now gives the log up to the last line end, for each wait and each place.
  128. 128. Pages on first use, by default (paging, second part). Memory_Create gives a memory object whose pages have no page frame. The first read or write of a page, by a program or by the kernel, gives it a zero frame. A page that nobody touches costs no frame and no zeroing time: the stack of a program (64 pages) and of a thread (16 pages), the second picture of a surface, the text region of a terminal (4 MiB) and the zero data of a module. The flag Frames_Now of Memory_Create gives each frame at creation, for a program that must not take a page fault later in a path with a time limit. The default is first use because the rings between programs, the surfaces and the stacks work the same with it: the first write of one side gives the frame, and the other side sees that frame. Measured in the machine test, with a copy of master before this step that prints the same count: at the first memory(), the desktop used 19 975 pages before and 14 582 after (27 % less, 21 MiB), with 5 518 pages paid and no frame yet; at the end of the test, 24 980 before and 18 376 after (26 % less), with 6 769 reserved pages.
  129. 129. No overcommit: the budget pays at creation, the frame comes at first use. A reserved page has a free frame at any time: the kernel keeps free pages = pages of all the budgets + reserved pages, and the console checks it at the end of the test. So no access to memory fails for lack of memory, and the kernel never has to choose a program to stop. The other design, a budget that pays at the first use, makes a page fault fail when the budget or the machine is empty: a program then stops at a random instruction, with no way to handle it. The cost: a page that nobody touches still takes room in a budget.
  130. 130. The frames of a memory object are not contiguous. The kernel keeps the frames in a frame list: in the record of the object for at most 4 pages, else in list pages of 512 frames, and a top page above them for more than 512 pages. The budget pays for the list pages (operation.memory_cost: 65 pages for an object of 64 pages). Memory_Create no longer gives No_Memory when the budget can pay, and the free frames have no order to keep. A memory object has at most 1 GiB. Three kinds stay contiguous, with each frame at creation: device memory (the framebuffer, the records of the log, the boot image, the time page), the thread area (the kernel reads the thread record and the kernel ring as one block through the direct map), and the module and the context of the root service (the kernel writes them at boot).
  131. 131. Two places give a page on first use. A page fault of a program with a page that is not present, in a mapping that permits the access, makes the page present and runs the instruction again. user_range makes each page of a range present before the kernel copies to or from a program. Any other page fault of a program stops it, as before: a write to a page without Write, and an address with no mapping. A page fault of the kernel at an address of a program still stops the machine: each path of the kernel that uses memory of a program checks the range with user_range first.
  132. 132. Why the page fault path is correct with several processors. (1) The page fault runs under the kernel lock, as each change of the page tables and of the mappings. (2) Memory_Map makes all the page tables of the mapping, and the budget of the program pays for them then: a page fault never takes a table and never fails. (3) A processor does not keep a translation of an entry that is not present. So an entry that becomes present needs no message to the other processors. Two threads that fault on one page at the same time: the second one waits for the lock, finds the entry present, and runs its instruction again. (4) An entry that is present changes only to 0, in unmap, and processor_sync waits for each processor that uses the table before the frames return: that path is the one of step 25. A thread that faults while another processor removes its mapping waits for the lock, finds no mapping, and its program stops (choice 108). (5) The frame of a page is in the frame list of the memory object, not in a program: a second program that maps the object finds the frame that the first one took, and the two see one page. The frame list changes once for each page, under the lock, and frees with the object.
  133. 133. A new mapping is checked against the mappings, not against the page table. An entry of a page on first use is 0 until its first use, so it does not show that the address is taken.
  134. 134. What the reports show. System_Report has reserved_pages and first_use_faults; Program_Report has present_pages, the mapped pages that have a frame. memory() prints the pages in use and the reserved pages. programs() prints the present pages of each program, and monitor shows them in place of the mapped pages. The program pages reads the page table itself, so its half of the program now shows only the pages that the program used.
  135. 135. The machine test waited for "compositor: first frame" before "compositor: surface console". The system does not fix this order. With pages on first use, the first frame of the compositor takes more time (the frames of its pictures come at the first draw), and the surface of the console came first. The step has any_order now (choice 19).
  136. 136. Shared code: the loader prepares a module one time, and each program of the module maps it. The program that starts programs keeps a table of prepared modules (system.Prepared_Modules), by the hash of the module file. A prepared module is one memory object with the segments that do not change after the load: the code, the constants, and the data that is read-only after the rebase. Each program of the module maps these pages with the protection of their segment. It receives a new memory object for each writable segment, with the bytes of the file, rebased for that segment only (module.rebase_range): the pages of zeros after them stay pages on first use. The kernel learns nothing of the module format: it maps a memory object with the rights of a handle, as before.
  137. 137. Nobody writes the shared code. After the preparation, the table keeps a copy of the handle with the rights Read and Execute only, and closes the handle that wrote. A program of the module has a mapping and no handle: a write to its code is a page fault, and the kernel stops it. The program that starts programs cannot write it either.
  138. 138. The starter pays, and a prepared module lives while a program maps it. The budget of the program that owns the table pays for each prepared module, one time; the budget of each program pays for its writable segments, its stack and its context. Each mapping of a program comes from the handle of the table (choice 105), so Handle_State gives the count of programs that map it. At each start, the loader closes each prepared module that no program maps. No new kernel operation is needed.
  139. 139. Three programs keep a table: the root service (each program of the desktop and each terminal), rings (its six node programs) and the console (its call hosts, which run the module host). program_start with no table gives the program its own copy of the module, as before: the program frames uses it to measure the difference.
  140. 140. The demonstration: frames and twin. frames reads its own page table (Inspect, kind Mappings) to count the pages of a range that have a frame: the answer comes from the tables, not from a counter of the kernel. It starts twin from the boot image in three modes (support:twins): wait, write a page that frames gives, write to its own code. It measures with the cycle counter of the processor and the frequency of the boot record. The cost of a module in pages is what the budgets of the twins paid (Program_Report.budget), plus the prepared module that frames paid.
  141. 141. Measured (machine test, QEMU with KVM, 4 processors, with the other programs of the scenario open). To create and map a memory object of 8192 pages (32 MiB): 2 560 us with Frames_Now, 74 us with pages on first use (35 times less). The first use of a page, with the page fault, the zeroing of 4 KiB and the return: 722 ns; a write to a page that has its frame: 7 ns. Four twins: 320 pages, each with a copy of the module, and 311 with the shared code: the module twin has 3 pages that do not change, and each twin after the first saves them. The modules of this system are small: the shared code of a module is 4 pages (node) to 28 pages (console), and 50 pages for temple with its portrait; each program of a module after the first saves that count. Pages on first use save more in modules: the zero data of temple is 142 pages, and they had their frames at the start before; now they have none until the program uses them. The start of one twin: 23 us with a copy of the module, 43 us with the shared code: the hash of the module file and the sweep of the table cost more than the copy of 3 pages. For a module of many pages the copy costs more than the hash.
  142. 142. A block service is a program with one device (programs/block). The root service holds the two ports of the configuration of the bus (0xCF8, 0xCFC) and no other program has them: a port gives the configuration of each device, so it cannot be given for one device. For a disk, the root service reads the configuration, turns off the line interrupt, sets one message interrupt in the table of the device (MSI-X), gives the device the right to use memory (bus master), and starts a block service with: the device memory of each BAR except the BARs of the table of the interrupts, the message interrupt, 8 pages for the queue, the machine with the right Physical only, and a copy of the configuration in one page. The block service reads the virtio capabilities in the copy and never writes the configuration: it cannot change its interrupt. "PCI discovery by a bus service" was the other way: it is the same code in another program, and the root service would have to give that program the ports and the right Devices. A bus service is the right place when the system finds devices that come and go; the first machine is fixed (P7).
  143. 143. Three additions to the kernel, the smallest that a device service needs. Device_Create has two new kinds. Memory makes device memory: the kernel refuses each page that the firmware gave as memory, so the right Devices cannot become a way to map the memory of the kernel or of a program; a mapping of it uses no cache. Message gives a message interrupt: one of 16 vectors (64 to 79) on the first processor, with the address and the value that the device writes; the kernel counts it and wakes the owner as for a line. The flag Contiguous of Memory_Create gives a memory object with each page frame from the creation, in one run of physical memory, and with no frame list; it can give No_Memory when the budget can pay (no run is long enough). The operation Memory_Physical (number 25) gives the physical address of the first page of such an object, or of device memory, to the holder of the new right Physical on the machine; it refuses any other memory object, whose frames come at first use from anywhere (choice 130). One address names all the pages, so a request to the device is one descriptor for its data, and the block contract has no list of pages. The other way, a physical address for each page of an object made with Frames_Now, needs one descriptor for each page of a request and a table of addresses in the block service, for no gain on the first machine: a device that the memory of a program reaches page by page is the case of an IOMMU, a later step. The queue of a block service and each data region are contiguous. Choice 34 planned message interrupts for the next device class; the kernel still has no I/O APIC code.
  144. 144. The machine of the test has three disks at fixed places of the bus (support/pci): the boot volume where QEMU puts it, the data disk at the place 16, the disk of the measures at the place 17. The tool and the root service read the two numbers from one package. The root service gives the boot volume to no service. The boot program knows the boot volume (the firmware loaded it): a later step can give its place in the boot record, and the root service then needs no fixed place for it.
  145. 145. The block contract is a ring and a data region (contract/block). A request is read, write or flush of blocks of 4096 bytes; values are the first block, the count and a place in the data region. The client gives the data region through the block ring, and the block service proves it with Handle_State, as for a lent handle. The device reads and writes the data region of the client: the bytes are never copied. Many requests are in flight; a completion has the tag and a status (Ok, Bad_Command, Out_Of_Range, No_Region, Device_Error, Unsupported). The block service takes a command only when the completion queue has room for it, and the client waits for room in the command queue: a slow client stops its own requests and nothing else. Info gives the size of the disk; Statistics gives the requests, the blocks, the kernel entries of the service and its interrupts, so that the client measures the cost of the two sides. An NVMe driver serves the same contract: nothing in it is virtio.
  146. 146. One block service for one client, started with its client. The root service starts the block service when it starts a program with the role of a disk, and stops it when the client stops. When a block service stops, the root service turns off the bus master of the device before it frees the memory of the queue: the device does not write in pages that another program receives later. The data region belongs to the client, and the block service has a handle to it while it runs.
  147. 147. A library counts the kernel entries of its program (system.kernel_entries). Each kernel entry of a program goes through system.enter, which adds 1 to a word with one atomic instruction. A program measures its own cost with no grant to inspect the machine; the block service gives its count in Statistics.
  148. 148. Found by this step: a Ports handle that a program received while the processor had its ports open did not open its ports. ports_switch does nothing while the program on the processor stays the same. The root service had opened the ports of the PS/2 controller before (it gave them to input): its new handle for the bus ports gave a protection fault. handle_insert now opens the ports of a new Ports handle on each processor that has the ports of the program open, as handle_close closes them.
  149. 149. Measured (program disk, the test machine, QEMU with KVM, one disk file on the host): with one request in flight, 13 000 to 18 000 blocks in one second, with 2 kernel entries of the client and 3 of the block service for each request (a wake and a wait on each side, and the interrupt). With 32 in flight: 165 000 to 300 000 blocks read and 110 000 to 175 000 written in one second, with 0.05 to 0.06 kernel entries of the client and 0.09 of the block service for each request: one entry serves 10 to 20 requests. Requests of 16 blocks, 8 in flight: 840 000 to 1 240 000 blocks (3.3 to 5 GB) in one second. The numbers are from two runs; the test asserts the lines, not the numbers.
  150. 150. The format of the data disk: copy on write, two places for the root block, a check in each pointer (programs/storage/engine.odin). Studied before the choice, for the rule "a stop of the machine never shows a part of a change": a journal (ext4, the rollback journal and the WAL of SQLite) writes each change two times and needs a replay at the start; a log-structured disk (LFS, F2FS) needs a cleaner; shadow paging with two meta pages (LMDB) needs neither; ZFS keeps the check of each block in the pointer that names it, so a block that a fault changed is found when it is read. The format takes the last two: blocks 0 and 1 are the two places of the root block (a generation, a check of its own, one entry for each private store); a store has one table block (one entry for each object); an object has one list block (the pointers of its data blocks). A commit writes the changed blocks to free blocks, flushes, writes the root block with the generation plus 1 in the place of the older root, and flushes. A block that the root on the disk names is never written. At the start the service takes the valid root with the highest generation, reads each block that it names and compares each check. A root that the stop of the machine cut has a wrong check: the root before it names blocks that no commit changed. So the disk always has the state of the last commit or of the one before. The old root names the state before the last commit: versions (P9) can grow from it, with a rule that keeps old blocks.
  151. 151. Property 9 says "the first version is SQLite on the raw block device". This step does not do that. The agent that built the step had a wrong rule from the coordinator, "Odin standard library only". The owner never gave that rule: the recorded rule is about the kernel tool only (corrected by the owner on 2026-11-10). So this reason is false, and nothing forbids SQLite. What stays true: the decision of 2026-11-09 also says "write a new engine under MIT", and this step writes it now, small (800 lines with its exports), in place of a first version that a later step must replace. The owner then decided on 2026-11-10 to keep this engine and to remove SQLite from the system (properties 9 and 12).
  152. 152. The private store is named by the root service, not by the program. The command Connect on the service ring has two more fields: the name of the caller as the root service names it (32 bytes), and a grant: an amount and the flag "acts for the person" (isolated.CALLER_AMOUNT_MASK, CALLER_PERSON). The storage service uses the name as the name of the store and the amount as its limit of blocks. An export takes the name of an object and never the name of a store: a program reaches its own store only (P8). A program that the root service starts again has the same name and finds its data. system.service_caller gives the connection of the call that runs. The fields are in the generic contract of a service: any service can know who calls it, and the caller cannot choose.
  153. 153. The storage service is a service with exports, as paint and tally: write, read, size, remove, objects, stores. The console calls them with connect("storage") and the descriptions of the file; a program calls them with support/storage. Each change is one commit on the disk when the call returns: the system has no save action (P9). One thread serves the calls one at a time; a call waits for its block requests, with all the blocks of the commit in flight at the same time.
  154. 154. A service that starts when a program asks for it (on_demand in the root service, P16 "hundreds of background processes"). connect("storage") starts the storage service and the block service of the data disk, then connects. The storage service reads the disk before it serves: the call of the first connection waits in its ring.
  155. 155. The limit of a store is a number of blocks (P20, line 16): its table, its lists and its data. A change that makes the store larger than its limit gives -2 and writes nothing. The root service gives each program its limit by name (store_limit_of): 4 blocks for the console, so that the test reaches it, 64 for the others.
  156. 156. The person sees each store and revokes a grant. storage.stores() lists each store of the disk with its objects, its blocks, its limit and the count of connections that hold its grant; only a caller with CALLER_PERSON sees it. grants shows "store", and revoke(name, "store") sends the command Grant with no amount for each connection of that program to the storage service: the store stays on the disk, the calls of the program give -1, and the program continues. The kernel does not revoke the memory of the connection here, because the program would stop on a page fault at its next call (choice 108); the store is data of the storage service, so the storage service is the place that ends the grant.
  157. 157. A program receives its store when it starts. A program with the role Store and not Person (notes) has its connection to the storage service in its context: the call ring, the call region, the storage service with the right Read, and the file of the storage service mapped read-only at storage.FILE_ADDRESS (support/storage). The root service starts the storage service first if it does not run. support/storage finds each export by its name in that file and encodes the call with its description, as the console does: the program has no copy of the numbers of the exports. A program with no ring to the root service cannot ask for other connections.
  158. 158. The persistent program writes each change (programs/notes): each key of the note is one commit of the object "note", and each step of the count one commit of the object "count". The count is three blocks that each hold the count in each of their 512 numbers: a store that showed a part of one commit with a part of another would show blocks that do not agree. One commit costs 3.1 to 3.2 ms in the test machine: two flushes, and QEMU makes each flush a sync of the file of the disk on the host.
  159. 159. The test of a crash ends QEMU, and the test of a cut write damages the disk. The tool ends QEMU in the middle of 1000 commits: the next start reads each block with its check and finds the last commit. Ending QEMU loses no write that QEMU already gave to the host, so it cannot cut a block; the tool cuts the root block of the last commit itself (root_cut): the next start does not use it and finds the commit before, with each block of it unchanged. The tool knows two facts of the format for this: the root blocks are blocks 0 and 1, and the generation is at byte 8.
  160. 160. A loader loads imports (support/system/load.odin). Before, system.program_start and system.module_load refused a module with an import, and the console and the call host had no way to find the file of an import. Now a module space holds the modules that a loader put in one program, each one time, with a count of uses. The loader puts the module first, then each import after it, then binds the slots of the module with the addresses of its imports. module_space_release removes a module with no use, and its imports lose one use. On a failure, the space is as it was before the load: the loader removes what it mapped and gives back the counts. With a table of prepared modules (choice 136), the pages that do not change after the load also hold the bound slots: a prepared module is for one base and one binding (the hash and the base of the module of each import), and the table keys it by the hash of the module, its base and its binding. A module that a program loads into itself (the console, a call host) has no table: it is a copy of its own, bound like the others.
  161. 161. The load source is a grant (P20). The loader finds a file only in what the party that starts the program gives: files in the form of the boot image. It checks the hash of each file before it uses it. The root service and the console give the boot image. A call host receives one page from the console, with the list of its files, and the files only: the module and its imports (system.source_closure).
  162. 162. Refusals name the module. A load that fails names the stage and the module, and the space is as it was before. 164 gives the form of the record.
  163. 163. load of a module that a module of the console imports gives the same copy: load("calendar") after load("agenda") adds the commands of calendar, and the two names call one copy with one set of data. unload of such a module removes its commands and keeps the module.
  164. 164. The error of a load is a record that the caller owns (owner, 2026-11-10: errors that are robust, descriptive and informative; no exception and no global error state). module_space_load, module_space_release and program_start take ^module.Load_Error and give false or a status. The record is in the contract library (contract/module/error.odin), because a call host gives it to the console in the call region: the stage that failed, the module that was being loaded, the import concerned, a replacement on the path, the export, the slot, and for a check the difference with its two sides (type, rights or count, record name). Names come from the load source. system.load_error_text writes it as lines; the console prints each line after "error: ". An import cycle needs a replacement, because an import names a hash; the loader marks a module that loads its imports, and refuses an import of it with the stage Cycle. The first completion of a call host says if it loaded its module, so isolate reports a load error at once. MODULE_CONTRACT.md, "The error of a load", lists what the record does not explain.
  165. 165. The kernel keeps the module records of each program (operation Program_Module, number 26; Inspect kind Modules). The identity of a program that runs must be visible to the fault report and to programs(), for each program and each loader: the kernel already keeps the name that the creator gives. A record is 80 bytes: the base, the size, the hash, and the hash that it replaces. A program has 16. The operation needs Control on the program, or 0 for the caller. The kernel refuses two records that share an address. The kernel writes the record of the root service from the hash of its entry in the boot image. A fault names the module of the instruction and its offset, and the module that it replaces.
  166. 166. The replacement table is one page of the root service. It gives the page with the right Read to the console and to the manual, and the records to the loader of each program that it starts. A count of versions lets a reader copy the records while the root service writes them: the reader copies again when the count changed. A replacement applies to the loads that follow it, not to a program that runs. The console gives the records to a call host on the page of its files, with the module of each replacement.
  167. 167. The check compares the descriptions of the exports that the slots use (module.functions_compare, which gives the first difference): the count and the types of the parameters, the rights of each Handle, each Record with module.records_equal, the result. The names of the parameters and the help can differ: they are for a person. The loader binds by name one time, at load: the slot has the address of the export of the replacement, and a call is the same indirect call.
  168. 168. The person sets a replacement by names, through the root service. Root_Command.Replace carries the name of the replaced module and the number of the entry of the other module in the boot image: a ring record has 56 bytes of values, and two names of 32 bytes do not fit. Only a program with the role Replace can ask (the console). The root service refuses a chain: one record for one import, so the loader follows one step.
  169. 169. The stop report. The kernel keeps, for each program that stops on a fault, the stop code, the instruction, the address and the module record of the module of the instruction (op.Stop_Report). Program_State writes it at an address that the holder of the program handle gives. A fault report then names the module that ran, from the record that its loader wrote: the console prints it for a call host.
  170. 170. Measured (program imports, in the machine test, the least of 5 runs): a call through an import slot costs 3.5 ns to calendar and 2.7 ns to calendar_fixed in its place, for 1 000 000 calls: the same instruction, with two bodies that differ. The bind of agenda costs 372 ns with a direct import and 891 ns with the check of the replacement of one slot: the check adds about 520 ns for one slot. The load of agenda and its import costs 117 us and 154 us, with the hash of each file. Over five runs of the test, the bind took 216 to 377 ns and 857 to 904 ns, and the load 95 to 126 us and 120 to 155 us.
  171. 171. The code of an imported module is one copy for all the programs of a table. Choice 136 prepared the segments that do not change after the load at one base: an import is at another base in each program (after the module of the program), so two programs that import calendar had two copies. A prepared module now has two parts. The code (the segments with no Write) has no rebase and no slot: the table keys it by the hash only, and each program at each base maps it. The fixed part (read-only after the load) has the rebases and the slots: the table keys it by the hash, the base and the binding. A table has 64 places, two for each module. Measured by imports: it starts itself again and agenda with one table, the two import calendar at two places, and the table has one copy of the code of calendar (2 pages), mapped 4 times (2 segments in 2 programs). frames counts the pages of the two parts. The cost: the twins of frames (a module with no import, at one base) paid 313 pages with one part, and pay 315 with two parts, in one run each.
  172. 172. A Table result (module.Type.Table, owner item 94 (1) of HANDOFF.md: many records in one call, in memory of the caller). An export gives records of one description that follow each other: the caller gives a buffer {address, capacity in records} after the parameters, and the callee writes the records that fit and gives the count of the full table. It is the Text result with records in place of bytes: the call form, the isolated call (isolated.result_table) and the service (export_serve) have one more case each, and no new mechanism. A negative count is an error that the documentation of the export names. A record of a Table has no Handle, as a Record result. In Odin: result: ^[]Name as the last parameter.
  173. 173. A Text field of a fixed size in a record (module.Field: the type Text, the size in the detail). A row of a table needs names and hashes, and a record holds values only: the text is in the record, UTF-8 and zero bytes after it, and holds no address, so the record means the same in each program and travels as bytes. In Odin a field [N]u8, N a number from 1 to 256. A hash is in a row as 64 hexadecimal digits: one field form for all texts, which a person reads and the console compares and joins; the cost is 32 bytes for each hash. The other form, a field of raw bytes, needs a second field type in the contract and in each reader.
  174. 174. The tool describes a record that a library declares. The rows are Odin structs of support/information; the manual exports modules :: proc "c" (result: ^[]information.Module) -> i64. The tool reads the imports of each file, and reads the structs of an imported package of support: or contract: when an export names one. So a row has one declaration, and each service that serves it has its description in its own file.
  175. 175. The information functions (support/information). Each one takes the catalog (the boot image that the program was granted, and the records of the replacement table) and a slice of rows of the caller; it writes the rows that fit and gives the count of the full answer; it allocates nothing and keeps no state. A row holds numbers and short texts read from the tables; a long text is a slice of the file (export_text): the file stays the one source. The rows: Module (name, hash, file, code, constants, data, zero, entry, exports, imports, records, and a problem), Export (module, name, number, parameters, result, rights, signature), Import (module, number, hash, name, slots, replacement), Slot, Segment, Running, Replacement. The functions: modules, exports, imports, slots, segments (one module by name or by hash, or "" for each module), and the reverse questions importers (by hash), exporters (by the name of an export), described (a text in the help or the documentation), handles (a right that an export asks of a handle), then running and replacements.
  176. 176. A malformed module gives a Problem, never a fault. The tables of a module are input that nobody checked. information.check tests each rule that the functions rely on, in order: the header, the range of each table of the directory, the segments, the entry, the exports, the record descriptions, the descriptions of the exports, each name and text in the string table, the documentation, the slots. It gives the first problem: its kind, the table, the record, the byte of the file, the bad value and its limit; problem_write makes a sentence of it. modules() gives a row for a malformed module with the problem in four fields; a function on one module gives the Problem; the manual gives -2 and problem(name) the sentence. odin test support/information has 12 malformed cases, each with its table, record and byte, and 20 000 files with random damage, read by each function with the bounds checks of the test build.
  177. 177. What runs comes from the module records of the kernel, not from a second table. The first design of this step had a program table that the root service wrote. Choice 165 gives the same facts for each program, also for the programs that the root service did not start (call hosts, the nodes of rings), with the hash that a replacement changed: one mechanism for one concept, so the program table was removed before its first commit. running(machine, catalog, rows) reads Program_Report and the Module_Report of each program slot. The grant is the machine with the right Inspect (P20): the root service gives it to manual now. The manual gives running() only to a caller that acts for the person (CALLER_PERSON): it does not widen its own grant to any caller (P20, line 12). The root service now gives CALLER_PERSON in each connection of such a caller, not only in a connection to storage.
  178. 178. The manual is the service of the information of the modules. It already had the boot image, the replacement table and the documentation of each module, and its exports are described: the console needs no code for each kind of row, and a program connects to it as to any service. A program that holds the grants itself compiles in support/information and calls the functions directly. 13 exports: modules, exports, imports, importers, exporters, described, handles, slots, segments, running, replacements, problem, overview. A Table result gives -1 for a module that the catalog does not have, -2 for a malformed module, -3 for a refused running.
  179. 179. The console holds a table, and a few generic commands combine tables (programs/console/tables.odin): where(table, field, value), sort(table, field), join(left, right, field), columns(table, "a b c"), first(table, count), rows(table), row(table, number). Each one reads only the record description, so it works on each Table of each service. join keeps each field of the left record and each field of the right record whose name the left does not have; join and columns write the new description in the format of the module contract, in the memory of the line: a header, a directory, and the tables Records, Fields and Strings. No second format and no query language exist. The count command is rows, because the module probe exports count, and a command of a module hides a command of the console with the same name. The console prints a table with a line of field names, one line for each row, and the count; a Text longer than 24 characters ends with "..".
  180. 180. The replacements are in the answers. An Import row has the module that the replacement table gives in place of the import; importers also gives the imports that a replacement changes to the module; a Running row has the hash that a replacement changed (Module_Record.replaces).
  181. 181. The window. The key Tab, or manual.overview(), shows the table of all the modules: the bytes of code, constants, data and zero data, the counts of exports and imports, the count of modules that import each one, and the count of programs that run it, with the totals; a module that a program runs is green, a library yellow, a malformed module red. The page of a module has three more parts: its sizes, the modules that import it (and through which replacement), and the programs that run it, with the slot, the address and the module that it replaces.
  182. 182. Measured (in the machine test, at the start of manual, the clock of 1 ms and repeats for 50 ms or more): all the 45 modules of the boot image and their 65 exports in 149 to 151 us, with the check of each file each time; one reverse question, who imports calendar (2 modules, one through the replacement), in 42 us. Two runs.
  183. 183. A wrong block under a valid root damages one object, not the service (programs/storage/engine.odin, walk, mount; found by the line-by-line read of Review 7). Before, a wrong block under each valid root stopped the service for every program. Now the walk continues: an object with a wrong list or wrong data is damaged, a store with a wrong table is damaged, and the others are served. The blocks that a wrong table or a wrong list named are not known, so they are free. A read checks the blocks again each time.
  184. 184. Which root the service takes. The valid root with the highest generation, when it names no wrong block. If it names one and the valid root before it names none, the root before it: the last commit was not whole on the disk. If each valid root names a wrong block, the newest one, with its damage: no root is better. Cost: a second walk, then a third one, in the rare cases.
  185. 185. A damaged object is never an empty object. size answers -8, read gives no bytes, objects() and stores() say "damaged". The client support/storage gives the status Damaged, also when the text of a read does not have the size that size gave. A write or a remove replaces a damaged object: a change does not need the old blocks. notes says in its window which object is damaged, and a key writes a new one. Before, a disk error on a read looked like an empty object, and notes wrote over its note.
  186. 186. The service copies the name of an object one time (name_keep), then checks and uses the copy. The bytes of a Text argument are in the call region, which the caller can change during the call. A name with a zero byte is refused (-3): a zero byte ends a name on the disk, and a first zero byte says that a place is free. The data of a write was already copied to the block before its check.
  187. 187. A format that a stop cut is formatted again. The first root goes to block 1 and the first commit to block 0. So block 0 of zero bytes and a first root with a wrong check prove that the disk holds no object. Before, the service refused such a disk for good.
  188. 188. The format of the data disk is a contract (contract/store): the records, the check, the validity of a root. A disk lives longer than the program that wrote it. The tool reads the same declarations to cut a root and to damage a block: before, the tool held its own offsets. The ERROR_ numbers of the service are in support/storage, for the service and the client.
  189. 189. One build gives one hash: the compiler runs with one thread (tools/kerneltool/modules.odin: -thread-count:1 -no-threaded-checker). The gap of step 34 said that the hash of a large module depends on the folder of the build. The cause was not the folder: with its threads, the Odin compiler puts the functions, the constants and the instances of a generic function in an order that changes from one build to the next. Measured: with neither option, 13 of 48 modules differ between two builds in one folder; with -thread-count:1 only, 4 or 5 differ; with the two options, 0 differ in three builds and 0 between two folders. The cost is none: 2.3 s for console, against 2.5 s. The boot program gets the same options and -source-code-locations:filename: its file is the same bytes in two folders. The check builds the modules a second time and compares each file. One name still changes in the object (a number in the name of a variable of the runtime), in the symbol table, which is not in a module. Corrected by 400 (2026-11-11): -thread-count:1 did not give one thread. The stock compiler starts one worker beside the main thread, and the rare differing builds came from that worker. The flake now patches the compiler, and -no-threaded-checker is gone (it changes only one phase of the checker).
  190. 190. The boot display is cards, a path and tiles, drawn with rectangles only (support/bootscreen/; owner, 2026-11-10: the display must be original, and the steps must be an interface, not printed text). The display keeps its rule: each thing that it shows is a record of the kernel or of the boot program. What changed is the form. The head has the path of the start: ten steps of the start as squares, full when done, a ring for the step of now, red when the start failed there, with the count and the sentence under it. The stages are one tube on a line of time, each with its duration inside, and each program of the start is a mark at its creation. Before the kernel measures the stages, the same card lists the ten steps with "done" and "now". The memory is one band by address. Each processor is a square. Each module of the boot image is a tile: its width is its size and its color is the first byte of its hash, so a module that changes takes a new color. All of it is fill of rectangles with a color that goes from top to bottom, with no round corner (owner, 2026-11-10: the owner does not want round forms), and the 8x16 font, also at two and three times its size. No floating point, no image, no new operation. Measured on the host, 200 draws of a 1024 x 768 screen with the same compiler options: about 0.5 ms for one draw, against 0.33 to 0.40 ms for the display before. A start draws the display three times.
  191. 191. The network is three programs, and the kernel has no network code (P1, P5, P7). The frame service (programs/frame) is the device service of the virtio network device. The network service (programs/network) is its one client. A program with the grant Network (net) has one endpoint in the network service. The root service finds the device at the place 18 of the bus and starts the frame service as it starts a block service (choice 142): the same grants, the right Physical, one message interrupt for the two queues of the device. The kernel did not change.
  192. 192. One package for virtio (support/virtio): the capabilities in the copy of the configuration, the agreement on the features, the split virtqueue (start, give, notify, used). The block service uses it now: 324 lines in place of 507. driver_start of the root service takes the class of the device (Driver: the program, the device number of the class, the ring and its role).
  193. 193. The frame contract (contract/frame) has the form of the block contract: a frame ring, and a frame region of the client that the device reads and writes with no copy; Attach through the ring, proved with Handle_State; many requests in flight; a command is taken only when the completion queue has room for it. A Receive is a place that the client gives before a frame arrives (P14): the network service keeps 128 in flight. The frame service does not read a frame: it computes no checksum and checks no address. The header of 12 bytes that virtio puts before each frame stays in the memory of the frame service; one header of zero bytes serves each Send (no offload).
  194. 194. Where the protocols run: the nearest form of property 5. Property 5 says "the network stack is a library in the program that uses the network", and U7 of ARCHITECTURE.md says that the network service gives a program a queue of the card and leaves the data path. This step does not do that, and says so. The virtio network device of QEMU has one pair of queues: a program must read each frame to give it to its endpoint (open question 3 of ARCHITECTURE.md: virtio-net under QEMU needs a software switch). That program must also check the source address and port of each frame that a program sends against its grant, or a program could send as another one. To check the headers of UDP and IPv4 costs the same as to write them, so the network service writes them, and a program gives the data and the remote. What keeps property 5: the code of the protocols is a library (support/network) that does no input or output; the kernel is not on the path of a datagram (measured below); QUIC will be a library in the program over datagrams, as property 5 asks. The plan for TCP: a library in the program over an endpoint of segments; the network service checks the IPv4 header and the ports of each segment against the grant, and keeps no state of a connection. A device with several queues and filters lets the network service leave the data path; the first machine has none.
  195. 195. A grant names one remote (P20, lines 3, 4 and 11). The root service names, for each program by its name (endpoint_of), one remote address and one remote UDP port. The program sends only there, and receives only from there; Resolve and Echo accept only the address of the grant. The network service chooses the local port (49152 + the place of the endpoint): a program does not choose a port and knows no global port name. Two other forms were weighed. A local port that receives from any remote (a server) opens the program to each machine of the network. A set of hosts gives more than a client needs. One remote is the least that serves a client: the nearest form of "sandbox by default". A server (a local port and the remotes that it answers) is the next form, with TCP. Names are not in a grant yet: no DNS.
  196. 196. The root service makes the datagram region, not the program. In the first version, the program made its region and gave it through the ring, as the data region of the block contract. Found before the commit: the program then holds the origin of the mapping of the network service. A revocation of its own handle ends that mapping, and the next copy of the network service is a page fault that stops it (choice 108): one program stops the network of all programs. The block service has no such case: its device uses physical addresses and it maps no region of its client. Now the root service makes the datagram ring and the datagram region (128 pages, pages on first use), as it makes the call region of a connection (choice 53), and gives both to the two programs through the control ring. Property 14 holds for the device: the network service supplies the frame region to the frame service.
  197. 197. One copy of the data, in the network service. The data of a datagram is copied one time, from the datagram region to the frame region, or back, by the network service: 64 to 1472 bytes. The headers and the checksum are computed over bytes that the program cannot change after the check. The device reads and writes the frame region with no copy.
  198. 198. Checksums in the network service, no offload. The network service measures at its start the headers and the checksum of a datagram of 1472 bytes: the log has "network: the headers and the checksum of a datagram of 1472 bytes: N ns": 600 to 800 ns in three runs. The round trip of a datagram is 60 to 201 us: the checksum is not the cost to remove. An offload also makes the frame service read the frames.
  199. 199. Errors are records. (Changed on 2026-11-11, 385: the values of a refused completion are now an error record.) A refused request has the detail of the refusal in the values of its completion: Not_Granted gives the remote of the request and the remote of the grant; Too_Large the count of bytes and the limit; Out_Of_Range the place, the count and the size of the region; No_Answer the address, the milliseconds and what did not answer. A frame from the wire that breaks a rule gives a datagram.Problem: the layer, the kind, the byte of the frame, the value and the limit (the record of the module information, choice 176, for frames). The network service counts each kind in each layer with its last Problem, and the command Problems writes the table in the datagram region (Problem_Row, 32 bytes): a table of fixed-size records in memory of the program; choice 205 says which rows an endpoint reads. network.problem_write makes a sentence of one: "ipv4, byte 24: the checksum is 0x77bd, the bytes give 0x22bd".
  200. 200. A frame from the wire never faults the network service. network.parse checks each length before it reads a field, and gives a Packet or a Problem. odin test support/network has two known answers (the sum of RFC 1071, an IPv4 header with its checksum), the frames that the library writes read back, 19 malformed frames each with its layer, kind, byte, value and limit, a sentence, and 20 000 frames with random damage and random ends, read with the bounds checks of the test build. In the machine, the peer sends 11 malformed frames, and the network service refuses each one in the expected row.
  201. 201. The peer: QEMU -netdev dgram to the kernel tool. The QEMU of the flake (10.2.4) has the backends user, socket, dgram, stream and others. User networking answers ARP and echo at 10.0.2.2 and gives a datagram to 10.0.2.2 to the loopback of the host, but the machine must know the port, and several tests run at the same time on one host: they would share it. guestfwd is for TCP only. With dgram, QEMU sends each frame as one UDP datagram to a port that the tool chose (port 0: the host gives a free one), from a port that the host gives. So no frame leaves the host, each run has the same answers, and tests that run at the same time do not meet. The peer (tools/kerneltool/peer.odin, core:net, one thread) is the gateway 10.0.2.2 and an echo server on port 7. It reads each frame with its own code, not with support/network: a wrong checksum of the library is a fault that the peer finds. The test fails when the peer receives one wrong frame, or when the machine did not answer the echo request of the peer. The cost: about 400 lines of the tool check the same rules a second time.
  202. 202. The network is a machine of its own in the test (scenario_network): the main scenario, that other steps change, does not change, and the frames of the peer go to one machine. The cost: one more start of the machine in each test.
  203. 203. Measured (the test machine, QEMU with KVM, 4 processors; ten runs, most of them during the tests of other agents): the round trip of a datagram of 64 bytes, 1000 times: 60 to 88 us at least, 83 to 201 us in the middle. With 64 datagrams in flight for one second: 57 886 to 81 733 datagrams sent and answered, 0 lost. Kernel entries for each datagram: 0.18 to 0.24 of net, 0.35 to 0.57 of the network service, 0.71 to 0.98 of the frame service: no kernel entry carries the data, an entry is a wake or a wait, and one serves several datagrams. The frame service enters most: one interrupt for each group of frames; it does not ask the device for fewer interrupts. The first ARP took 1.9 to 2.8 ms, the echo 192 to 302 us, the first datagram 221 to 316 us. The round trip includes the peer, a thread of the tool on the host, and two passes through QEMU.
  204. 204. The grant Network is revoked by the network service (Root_Grant.Network). revoke("net", "network") makes the root service send Endpoint_Grant with no remote. The network service then refuses each request that names a remote (Not_Granted with the grant 0 and 0), and gives no datagram to the endpoint. The endpoint stays, as a store stays after its grant (choice 156). The console prints "net lost the grant network, holdings ended: 1": the count is of endpoints, and the console names each count "holdings".
  205. 205. A refused frame belongs to one endpoint or to the machine, and no endpoint reads the row of another (P20, line 1; correction of the coordinator before the merge). The first version had one table for all endpoints: a row Not_Granted held the port of the grant of another endpoint in its limit, and its count told a program of the traffic of another program. Now a datagram that reached the local port of an endpoint from a remote that its grant does not name is counted in the table of that endpoint (scope Endpoint), with the datagrams dropped with no place. A frame that no endpoint owns (a malformed frame, a port of no endpoint) is a fact of the machine (scope Machine). An endpoint reads the facts of the machine only with the right Inspect of its grant (datagram.GRANT_INSPECT): Problems then also gives the rows of the machine, and Statistics the kernel entries of the two services and the echo requests answered; without it they are 0. The root service gives this right only to a program that holds the machine with the right Inspect, which reads the reports of all programs already: the right adds no fact that the program cannot read, as running() of the manual needs a caller that acts for the person (choice 177). The other form, the facts of the machine on the log only, was refused: the log is not an output (choice 51), and net must show its cases. problem_rows (support/network/tables.odin) takes the table of the asking endpoint and the table of the machine only: it cannot reach a table of another endpoint. A host test has two endpoints and the machine: the endpoint without the right reads no row, with the right it reads the rows of the machine and not the row of the other endpoint.
  206. 206. The table of hardware addresses learns only what the network service asked (network.neighbor_learn). An ARP frame from an address is taken only if a request of this service waits for that address (a Send, a Resolve or an Echo before its echo request). So an ARP frame that nobody asked for changes nothing, also not the entry of the gateway; an ARP request for the address of the machine gets its answer and teaches nothing. When the table is full, the answer replaces the oldest entry whose address no waiting request uses; when each entry is in use, the answer is not kept, and the waiting request still gets it. Before, the 17th address replaced the first entry in silence. Three host tests: not asked, a false answer for the gateway, a full table with entries in use.
  207. 207. The window of net follows DESIGN.md where it is cheap now: rectangles only, positions on the grid of 8 pixels, the colors of the rules (background, card for one band of two), the name of a case and of a measure in the dim color, a value in the text color, the answer of a case in the color of a program that runs when it is the expected one and in the color "failed" with the words "not expected" when it is not. No card, title or caption yet: the window keeps its form.
  208. 208. The time page has the frequency of the cycle counter. Time_Record.cycle_frequency is the cycles in one second, as the kernel measured them at boot; system.cycle_frequency() reads it with no kernel entry. The reason is a failure of the machine test: with a host load average of 24, limits printed "used of limit: 72550 of 50000, 273981 of 200000, 841881 of 600000, the program 2121319 of 1500000", each budget about 1.4 times its limit. The kernel was right: it counts processor time with the cycle counter and gives a budget its part of the true length of a period. The program was wrong: it divided that time by an interval in milliseconds of the time page, which count ticks, and the ticks came late. limits now measures its interval with the cycle counter. The frequency is a fact of the machine that a program can already measure (the cycle counter against the milliseconds), so the page gives no new knowledge (P20), and a program no longer needs the machine with the right Inspect only to measure. Gap: the milliseconds of the time page still lose time when ticks come late.
  209. 209. The notice of a virtqueue is the number of the queue. virtio.notify wrote 0 to the notice register of each queue. The rule of virtio 1.x (4.1.5.2) is the number of the queue. QEMU finds the queue from the address of the register and ignores the value, so the send queue of the frame service (queue 1) worked in each test. The queue now keeps its number and writes it. Found by the read of Review 8, after the merge of step 40.
  210. 210. The kernel lock is split by data (owner decision "Full queues and speed", item 8). This replaces choice 30. ARCHITECTURE.md, "Locks of the kernel", has the table of all kernel data, with who reads and writes it, from which path, and the lock or the rule that protects it. Eight kinds of lock, in one order: control, program (one for each program), origin, region (one for each memory object), frame, log, wait (one for each bucket of the wait table), run (one for each processor). A processor takes a lock only if it holds no lock of a later kind; the kernel checks it at each take and stops the machine with a report if not. So no deadlock exists by construction. The check costs about 10 % of a cheap kernel entry (measured: 2.19 to 2.40 million entries in one second with one locker without it, 1.51 to 2.07 million with it); it stays on.
  211. 211. A lock is a ticket lock. Processors get it in the order of their request. With the old lock (one exchange in a loop), the two threads of the word wait made almost no progress while two lockers entered the kernel. A processor that waits for a lock answers processor_sync, so that two processors that synchronize each other do not wait for ever.
  212. 212. The control lock is the one coarse lock, for the paths that cross programs: create, start and stop a program; create and stop a thread; give a handle to another program; map or unmap in another program; revoke; close a handle of a program, a thread, a budget, an interrupt or ports; create a budget or a device object; read the state or the report of another program; the stop on a fault. It stays in the final design: these paths reach an unbounded set of programs (a revocation follows the origins through any program, a stop closes handles that stop other programs), one order among them makes each one atomic for the others, and they are rare (property 3). An operation asks for it before it changes anything, and runs again with it (STATUS_RETRY, never seen by a program). Measured: in the measures with 4 lockers the control lock is taken 1 time with only the desktop (by another program), 53 to 64 times in the machine test (the reports of monitor), and it waited 0 times; the lockers never take it.
  213. 213. A lock for each program protects its handles, its mappings and its page table. An operation of a program on its own objects takes it and nothing global: two programs never wait for each other there. Two program locks at one time only under the control lock. The lock is re-entrant on one processor (a stop inside a close of the same program), and it is out of the program record, so a new program in a slot does not reset it.
  214. 214. A running thread belongs to its processor. Another processor that stops it sets stop_requested and sends an interrupt; the processor ends the thread itself. Before, the stopper wrote 0 in the current thread of the other processor and waited for it in processor_sync, which needed the one lock (choice 32; processor_sync now only makes processors reload their page table). A program ends when the last processor that runs one of its threads leaves it: holds counts them, and the stop adds a mark in the same atomic addition, so exactly one processor cleans the program up, after no processor uses its page table.
  215. 215. Pages in one section of the frame lock. The frame lock protects the frames, the reserved pages, the pages of the budgets and the slots of the memory objects. Each move of pages is one section, so System_Report reads "free pages = pages of budgets + reserved pages" exactly, at any time, and locks checks it with four programs that create and free memory. A frame is zero filled after the section. A page fault takes the lock of its program in place of the kernel lock: the five points of choice 132 hold with it. A lock for each memory object gives a page on first use one frame when two programs fault on it at one time.
  216. 216. The wait table. A thread that waits on a word has an entry in one of 64 buckets, chosen by the physical address of the word, each with its wait lock. A wake looks at one bucket, not at all threads. An entry carries the wait serial of its thread, which changes when the wait ends: a late wake of an old wait does not count a thread that runs, so Word_Wake(word, 1) does not lose its one wake on a thread that does not wait.
  217. 217. The tick takes no global lock. It adds the processor time to the budgets with atomic additions. The log limit is one word with the second and the count; a record is accepted only if each budget of the chain counted it under its limit. A budget that ends keeps its parent in its slot, and the slot serves again only after processor_sync: a tick that walks through it has finished.
  218. 218. The origin tree has its own lock. A revocation holds the control lock, sets revoking, and ends each holding under the lock of its program, after it checked that the tree did not change. While revoking is set, a map or a copy with no control lock does not add a holding: it runs again after the revocation. So a program cannot keep a holding by copying it during a revocation.
  219. 219. Ports. A processor writes only its own port bitmap, in run, when the program or its ports_serial changed. A change of the Ports handles of a program adds 1 to ports_serial and synchronizes all processors. Before, a processor wrote the bitmap of another processor.
  220. 220. The scheduler has one run queue for each processor (kernel/schedule.odin). The choice of the next thread is in queue_pick and schedule_pick: the step of the scheduling strategies changes them. A processor takes a thread from another queue when that processor has a higher load than its own queue: the first rule (a difference of 2) failed one test run of three ("at most 3 of 4 processors ran the program at the same time"), with two threads of processors in one queue.
  221. 221. The measures and the counts (program locks, kerneltool measure, choice of the owner "the test measures"). Before the split (the kernel of master, three runs with only the desktop): kernel entries in one second 2.53 to 2.70 million with 1 locker, 3.54 to 3.68 with 2, 2.78 to 3.25 with 4; pages on first use 1.42 to 1.61 million, 1.96 to 2.03, 1.62 to 1.82; the word wait 60 000 to 64 000 wakes in one second alone, 94 000 to 116 000 with two lockers. After the split (three runs): kernel entries 1.51 to 2.07 million, 3.31 to 3.79, 6.68 to 7.24; pages on first use 1.15 to 1.37 million, 1.84 to 2.00, 3.32 to 3.63; the word wait 466 000 to 556 000 alone, 227 000 to 327 000 with two lockers; 0 wakes lost. In the machine test (with the other programs of the scenario, three runs): kernel entries 1.06 to 1.56 million, 1.55 to 3.09, 3.64 to 4.38 (before: 1.70 to 1.97, 2.76 to 3.46, 2.30 to 2.78); pages on first use 0.69 to 0.97 million, 0.64 to 1.81, 1.28 to 2.05. What scales: entries on own objects scale with the processors (4 lockers: 3.5 times 1 locker); the lockers wait 0 to 3 times for a program lock in a measure. What does not scale: one cheap entry costs more than before (more locks, the order check); pages on first use scale less (2.8 times with 4), because the frame lock is one lock for all the frames: it waited in 16 to 26 % of its takes with 4 lockers; the word wait with two lockers in the machine test falls to 186 to 2 690 wakes in one second, because a woken thread waits in its queue for the end of a slice when each processor runs a thread (a gap of the scheduler). The test checks the counts, not the speeds.
  222. 222. A defer inside an if block ran at the end of the block, not of the procedure: the first version of locks waited for its lockers before its measure of the word wait, and measured 0 wakes. Found and corrected before the split; the numbers of master above are from the corrected program.
  223. 223. A review found seven faults that the tests did not reach; each one was real (2026-11-10). (1) A thread was Ready, in no queue and on no processor between two sections of the run lock: a stop ended it, the cleanup freed the page table, and a processor ran it. Now queue_pick takes the thread and gives it to the processor in one section. (2) The close of the last handle freed the slot of a Stopped program whose thread still ran; its cleanup then freed it a second time. Now a slot is free only after the cleanup (cleaned). (3) Thread_Create made a thread in a program that a stop had marked. Now it gives Bad_State. (4) A processor that ended its thread finished it after a wait for the control lock; in that wait the cleanup could finish it and a new thread take the slot. Now the serial of the thread is checked. (5) The references of a budget were a plain count under the control lock, and a copy of a Budget handle added 1 atomically with no control lock. Now the count is atomic. (6) The undo of a mapping that failed on a later page cleared entries with no processor_sync (also on master before the split). Now it synchronizes as unmap does. (7) program_event of a program could read a slot of a finished thread with the old program and the new events of a thread of another program. Now thread_finish writes program 0 under the program lock. The search for the same three forms (ARCHITECTURE.md, "Three rules") found two more: (8) the revocation checked the tree from the time of its first step, not from the choice of the holding, so it could close a new handle in the slot of a closed one; (9) Thread_Stop and Program_Stop returned while a stopped thread could still run some instructions on its processor (since the split): thread_end of the library then unmapped the stack under a running thread. Now the operation waits, with no lock, until the threads left their processors (stop_wait). And stack_guard, in a report, could make a table of the kernel half: now it only reads.
  224. 224. The stress case of locks. After the measures, 40 rounds of 3 churn lockers (locker with the role Churn): each one has two threads that compute, and a first thread that makes threads that end at once and stops and makes again a computing thread. The second and the third locker of a round stop the one before them after 1 to 4 ms. locks stops each one, or a second thread of locks stops it while the first thread closes the last handle, or the close of the last handle stops it. At the end the slots of programs and threads are back in the budget of locks exactly, and the free pages are the pages of the budgets and the reserved pages. The budget of locks is short of 2 pages: page tables of locks itself (the loader maps each module in it at a new address, and Memory_Unmap does not free an empty table); the check permits fewer than 30 pages (a quarter of one page for each stopped program). With the check of finding (2) removed, one run of two failed.
  225. 225. The window of locks follows DESIGN.md where it is cheap: rectangles only, positions and lines on the grid of 8 pixels, the colors of the rules (background, card for one band of two), the name of a row in the dim color and a value in the text color, the result in the color of a program that runs, or in the color "failed". No card, title or caption yet.
  226. 226. A second read of the corrections found one more fault. program_stop returned at once for a program that another stop had marked, so a second Program_Stop, or the close of the last handle, returned while a thread of the program could still run on its processor. The second stop now records the program as the first one does, and stop_wait waits until the threads left. stacks_free no longer makes a kernel page table while it frees. The coordinator corrected both from the report of the reviewer.
  227. 227. The error record: one form of 56 bytes, seven Integer fields (contract/errors; owner, 2026-11-10: errors that diagnose, no exception, no global errno). The fields: kind, unit, operation, argument, value, limit, found. 56 bytes are the values of a completion record, so a kernel operation needs no other memory. Integer fields only make it a described record (module.Record): the console and any language read it with its description. It has no field for the source: the caller knows where it called. It has no name: the names of a kernel operation and of its parameters are in the operation table, those of an export in its description. The design is section 8 of MODULE_CONTRACT.md.
  228. 228. A kernel operation gives its error record in its completion record. The direction told to the owner was "a record for the thread, in memory of the thread". The completion record is in the kernel ring of the thread, so it is memory of the thread, and it is the answer of one command: a batch with several failures has one record for each, and no value outside the call exists (the defect of errno). The kernel finds the record after the operation failed (kernel/diagnose.odin, about 400 lines): it reads the form of each parameter in the operation table and checks the parameters in order, as the operation does. The operations do not change; the success path has one more comparison of the status, in enter. An operation that the table does not explain gives the kind Refused with the status.
  229. 229. One operation table (contract/operation/forms.odin): the name of each operation and the name and form of each parameter (a handle of a kind with rights, a handle or 0, an address, a count with a limit, flags, a choice). The kernel reads the forms; a program reads the names for the sentence.
  230. 230. A handle slot keeps how its handle ended: closed or revoked, in a byte of the slot that was padding, until a new handle takes the slot. A handle of the generation before then gives "was closed" or "was revoked"; an older handle gives "ended, and its place has a newer handle".
  231. 231. No ambient fact in a record (P20): the record names the handles of the caller and what it gave. It gives what a budget holds only when the handle has the right Read (else the kind Budget_Hidden), and the state of a program only when the handle has Read. A failure of Handle_Give through memory that no one other program maps or holds says so, and names no program.
  232. 232. An export declares its error record as its last parameter, error: ^errors.Error_Record, after the buffer of a result. Function.error_record (the field reserved_details before, zero in old files) is the index of its description. A direct call has one more register; an isolated call has one more field of the argument record: the offset of 56 bytes in the call region, before the buffer of a result. The receiver gives the export the address in the region: the export only writes it, so no copy is needed. The export writes the record when it fails, and only then; it fails when its integer result is negative; an export with a Record result has none. The receiver writes the number of the export in operation. The check of a replacement compares the error records (Difference.Error_Record).
  233. 233. The storage service gives an error record for each of its 8 answers. read now fails with -7 or -8, in place of an empty text that size explained; a wrong name gives -3 in size and read too, where it gave -7. The client support/storage has the status Failed and gives the record to its program; notes writes the sentence on the log. The answer -4 (more than 32 blocks) cannot come from an isolated caller: the call region (64 KiB) holds less than 32 blocks (128 KiB).
  234. 234. The information exports of manual give error records: exports, imports, importers, handles, slots, segments and running, with the kinds Absent, Malformed (the kind of the problem and the byte of the file), No_Grant and Refused. The documentation of each export names its kinds, and manual shows it.
  235. 235. The console writes the sentence: "error: storage.write: the argument name is empty; it must have 1 to 39 bytes (-3)". The number stays at the end: it is the answer of the export.
  236. 236. The load error stays its own record (module.Load_Error, 288 bytes). It holds the names and the hashes of the modules of a load, which the caller of a call host does not have; 56 bytes cannot hold them. It has the same form of answer: values and fixed texts that the caller owns, no pointer, zero for no error.
  237. 237. Each module keeps its frame pointers (rule 8 of MODULE_CONTRACT.md, not enforced before). Odin has no option for it, and with -o:speed it kept rbp in 76 of 158 functions of console. The kernel tool now asks Odin for LLVM IR, and compiles it with llc of LLVM 18, the LLVM of the Odin compiler, with --frame-pointer=all. The flake has llvmPackages_18.llvm for it. Measured: console keeps rbp in 147 of 153 functions (the 6 others are leaf functions in assembly); its code is 172 773 bytes, 0.45 % more; with --function-sections, fault lost 27 % of its code, and kernel, manual, root and storage lost 0.2 % to 1.7 %. The build of all the modules takes 37 s, as before.
  238. 238. The stack trace of a fault (op.Stop_Report, 880 bytes, 800 more): the kernel walks the frame pointers of the thread that faulted, 16 frames at most. It stops at an rbp that is 0, not aligned on 8, not above the one before, or not memory that the program can read. Each frame has the address and the base and hash of the module record that holds it. The console prints one line for each frame of a call host that faulted.
  239. 239. The names of the functions come from a tool. A module names its exports only; the names of its other functions are debug information, in a separate object (decision 5 of MODULE_CONTRACT.md), which the system does not load yet. The kernel tool reads them in build/modules/<name>/linked.elf, where the offset of the module is the address: the step Trace of the machine test names each frame of the trace of fault and checks fault::three fault::two fault::one entry.
  240. 240. The program errors: one case for each kind of error, 19 cases, run at its start and again by a key or a click. Each case makes the error for real and shows the sentence of the record. math.square_root is a module export with an error record, for the direct call; fault now writes to address 0 three calls deep.
  241. 241. Measured (program errors, in the machine test, 200 000 kernel entries, the least of 3 runs, on a computer where other machine tests ran): a kernel operation that succeeds (Handle_State) took 540 ns, one that fails with its error record (Memory_Size on a budget) 525 ns. The time of a kernel entry hides the cost of the record. A direct call of square_root that succeeds took 39 ns, one that fails 3 ns: the failure returns before the work. Before and after this step (the machine test of master two times, of this branch one time, on the same loaded computer): a copy of a handle and its close (origin, 2 kernel entries) took 1250 and 1100 ns before, 1000 ns after; clock made 2 124 942 and 2 015 348 kernel entries in one second before, 1 779 861 after; a call through an import slot (imports) took 3.6 and 3.7 ns before, 4.1 ns after; the picture of processors with 1 thread took 443 and 446 ms before, 438 ms after. The two measures of the kernel entry move in opposite directions by up to 20 %: these runs do not show a cost of the success path, and they do not prove its absence to better than 20 %. The success path of the kernel has one more comparison of the status. The call through a slot is about 0.4 ns slower: the frame pointer of the called export (a push and a pop).
  242. 242. The wrappers give zero values and the record on a failure (found by the coordinator at the reading of the branch). The record in the values of a completion made each wrapper give a false result on a failure: memory_create gave a handle that was not 0, memory_size the kind of the record as a count of pages, and ring_open trusted it. Now system.call gives zero results when the status is not Ok, and system.call_with_error and the last parameter error of each wrapper give the record in memory of the caller. The cost on success: none, the branch is on the status that the call already reads. block, frame, origin and the root service read results of system.call without the status: they read 0 on a failure, as before the step. The program errors uses the wrappers.
  243. 243. The window of errors follows DESIGN.md where it is cheap: rectangles only, the colors of section 4 (a name dim, a value and the sentence of a record in the text color, a wrong answer in the color "failed"), each position and size on the grid of 8 pixels, and a caption that names the records (errors.Error_Record, module.Load_Error, op.Stop_Report.frames). Not done: cards with titles and the gradient of a surface; the constants of the design are in support/bootscreen, not in support/graphics.
  244. 244. The reads of diagnose take the locks of the lock split (choices 210 to 226). Each read_* takes the locks that the table of ARCHITECTURE.md names, in the lock order, and frees them before it returns; diagnose runs with no lock held (the test build checks it), and only on a failure, so a success takes no lock of it. A read through a handle keeps the program lock of the caller while it reads the object: the slot cannot serve another object. The reads that take the control lock: a budget, the log count, the state of another program. Handle_Slot.ended is written under the program lock: handle_close runs in command_run, in holding_end and in program_cleanup, each under the program lock. The stack trace runs in program_fault, under the control lock and the program lock of the program that faulted: user_range and user_copy run under that program lock, as the rule of the page tables asks. program.stop is written there, and read by Program_State under the control lock and the program lock of the program. A bug found on the way: after the merge, the log count read the word of the lock split (the second and the count) as a count; it reads the count of this second now.
  245. 245. A case under contention (program errors): for 500 ms, two threads make failures (a map to write of a copy with Read, 64 pages of a budget of 2, the size of a handle that does not exist) on handles that a third thread closes and makes again. The program checks that each record explains its status, as section 8 of MODULE_CONTRACT.md states. The lock-order check of the test build is on. The log has "errors: under contention: N failures from 2 threads while a third thread closed and made N handles; 0 records contradict their status".
  246. 246. A refusal takes no global lock that it does not need (finding E1 of an independent read). After the lock split, each refused Log took the control lock and the frame lock to read the count of its budget, and each refused Memory_Create the control lock: a program over its log limit that logs in a loop made the page faults of all programs wait for the frame lock. Now the count of Log is read under the program lock only: the handle with Read keeps the budget live, program.budget is an atomic word that changes only when that budget ends (budget_release stores it atomically now), and the limit of a live budget never changes; the table of the locks in ARCHITECTURE.md says it. A refused Memory_Create reads the pages under the program lock and the frame lock, with no control lock. Measured by errors (100 000 refusals each, the counts of System_Report.locks of all the processors during the loop, one run each, no other machine test on the host): a refused Log took 530 ns with the control lock taken 100 035 times and the frame lock 100 059 times before, 520 ns with 29 and 60 after; a refused Memory_Create took 480 ns with the control lock 100 006 times and the frame lock 200 001 times before, 470 ns with 12 and 200 001 after (one frame lock in the operation, one in the record). A refused Log needs a program with a log limit of 0: errors starts a copy of itself with that budget, which measures and writes the time in a page that the two map.
  247. 247. A kind is written only when its cause holds now (finding E2): a Budget record has a limit above what the budget holds, an Over_Limit record of Log a count above the limit, and the other kinds that were written with no check (Overlap, Absent of Memory_Unmap, Bad_Address of a range or of a word) read their cause again; else the record is Refused with the status. The case under contention of errors checks the stronger promise. A full table is named only when it is full now and is one cause that the state shows (E3): Thread_Create tests the thread areas of the program, its mappings, the memory objects, then the thread table; Memory_Create, Program_Create, Budget_Create and Device_Create test their table; a table of another program gives Refused. Without Read on a budget, the record depends on the operation alone (E5). The first stop of a program keeps its code, its report and its stack trace: a fault of a program that a stop or another fault already stopped writes nothing (E4; the overwrite was on master before this step). A closed handle of the generation 0xFFFF is Handle_Ended (its slot has the generation 0).
  248. 248. A table of the machine is named only where it is the one cause. After the correction of finding E3, slot_explain took the control lock on each No_Slot failure and told the caller of Thread_Create which of two tables of the machine was full (memory objects or threads). The status does not give that fact (P20). Now the record of Thread_Create and Program_Start names a table of the caller only, else Refused; the control lock is taken only for Program_Create, Budget_Create and Device_Create. Found by the read of the coordinator before the merge.
  249. 249. The milliseconds of the time page come from the cycle counter. Before, the first processor added 1 at each tick: a tick that came late lost time, and under QEMU the time of the system ran slow. Now the tick writes (cycle counter - cycle_start) / cycle_frequency, in milliseconds (milliseconds_now): a late tick loses nothing, and the value is at most one tick old. The seconds and the rest are divided apart, so no product overflows. The kernel word ticks is now time_milliseconds; the deadlines of the threads, the second of the log limit and the time of a kernel entry read it. A budget period starts at the first tick 100 ms or more after the period before (period_time), not when the count is a multiple of 100: the count can now pass over a multiple.
  250. 250. The time page has the cycle counter at the time 0 (Time_Record.cycle_start), beside the frequency. system.nanoseconds() reads the cycle counter and these two constants: a time finer than a millisecond, with no kernel entry and no wait for a tick. It assumes what the kernel already assumes for the budgets: the cycle counters of the processors agree. clock compares each read of the page with that time for half a second. It fails if the least that the page was behind is more than 1 ms: just after a tick the page is less than one tick behind, so more is lost time. It fails if the page was more than 1 ms ahead. The most that the page was behind is written and not checked: it is how late a tick came, which is the load of the host (13 ms in one run with a host load of 35).
  251. 251. Three corrections in the test of the errors (found by the read of the coordinator after the merge of step 42). The time of a failed direct call was measured with a loop of 4 to 6 ms on the clock of 1 ms: now system.nanoseconds(). The case under contention accepted each record Refused: now it counts them, writes the count, and fails when they are half of the failures or more. The LLVM IR that Odin writes in the root of the repository for a moment is in .gitignore.
  252. 252. The host encodes, the remote service relays (MODULE_CONTRACT.md, "A call from the host"). The host has the module files, so it has the description of each export: the tool parses the text of a call, checks it against the description and writes the argument record with isolated.encode. The remote service reads no description: it copies the bytes at offset 0 of its call region, sends the command record, and sends back the bytes of the places that the request names. The service checks the call region as for any caller. A relay that interprets would need a second reader of the descriptions in the machine, and a way to send names and types to it.
  253. 253. The format contract/remote: a request header of 104 bytes and an answer header of 128 bytes, fixed, little-endian, no text but names of 32 bytes. An answer has parts of 1344 bytes at most, with the total and the offset of each part: a result of the whole call region (64 KiB) is 49 datagrams. The request gives the unit of the integer result in the place of the result (1 for a Text, the size of a record for a Table, 0 for a Record); the remote service sends min(result, count / unit) * unit bytes, so a false count of the service sends no byte out of the place. A refusal of the remote service is an error record whose argument is the field of the request (REQUEST_FIELD_NAMES). Unit tests: a round trip, each refused field, the bytes of a result, the parts of an answer, and 200 000 random datagrams.
  254. 254. A request number, and the last answer kept. The tool sends a request again after 300 ms with no complete answer, and gives up after 10 s. The remote service keeps the last answer (64 KiB and its header): a request with its number gets it again, and the call does not run two times, so storage.write from the host makes one commit. The first number of the tool comes from the clock, so a new run of kerneltool call does not meet the number of the answer that the remote service keeps.
  255. 255. The path through the peer. The remote service sends to one remote, the host at 10.0.2.2 port 7100, the address of the gateway: the peer of the tool is that host. It learns the port of the endpoint from the first datagram of the remote service (the status Started), receives the call requests of the tool on a UDP socket of the loopback (port 7101 with kerneltool run, a port of the system in the test), and gives each datagram of the remote service to the program that sent the last call request. No new device and no second network: the machine has one network device. The peer sends the six malformed call requests once the remote service started, as it sends the malformed frames to the network service.
  256. 256. The grant Connect of the root service: a ring to the root service that serves Connect and Disconnect only, to the services of callable_of (remote: manual, monitor, storage). The ring of the console serves each command (Root_Ring); a program with Connect cannot start, stop, revoke or replace. The remote service connects at the first call to a service, so a service can start after it; a service that stops ends its connection, and the next call connects again. A call has 5 s: after it, the remote service ends the connection (Disconnect) and answers Over_Limit in milliseconds.
  257. 257. The grant person, and its revocation. The remote service acts for the person (isolated.CALLER_PERSON): the developer at the host is the person of the machine, and a smaller grant makes the tool see less than the console. The grant is now a recorded grant of the root service for each program (Root_Grant.Person, "person"): grants("console") shows it too, and revoke(name, "person") sends Service_Command.Grant with the new grant to each service that the program is connected to; a later connection has no person either. UDP has no authentication: see the gaps.
  258. 258. The monitor becomes a service with four exports: programs(), counts(), count(name) and served(). Reason: the monitor already holds the machine with the right Inspect and reads these reports for its window, and the owner named monitor.programs(); a new program would need the same grant again. The first three give facts of other programs and of the machine, so only to a caller that acts for the person (P20, line 1); served() gives facts of the monitor itself, to each caller. A Record result cannot fail (section 4), so the counts of the machine are a Table of named counts, and count(name) gives one of them as an Integer with an error record. The monitor serves the calls between its pictures: it waits on its connections and its window until the next picture.
  259. 259. The sentences compile on the host (support/sentence): the writer of texts, the names of the rights and types, and the sentences of the error records moved out of support/system, which keeps each name as an alias (system.Writer, system.error_text, ...). support/sentence calls no kernel operation, so the kernel tool compiles it: the tool prints the sentence of an error record with the code of the machine, not a second copy.
  260. 260. Two units of the error record: Connection ("a connection to this service", for No_Grant) and Milliseconds.
  261. 261. The window of remote follows DESIGN.md: three cards (the calls of the host with the large count, the connections with the color of the hash of each module, the last calls with their state in the color of a program or "failed" and the same fact in words), the grid of points, rectangles only. It draws at most ten times in one second, so a burst of calls does not wait for it. The window names an export by its number: the remote service does not read descriptions. The time of one picture is on the log: 119 us in the last full check ("remote: the window drew one picture in 119 us").
  262. 262. The machine test: the step Call (expected, outcome, rows_logged, repeat, parts) and kerneltool test network, which runs the machine of the network only. The Wait of "root: revoked the grant network of net" is any_order: the network service can write its line first (seen one time in this step).
  263. 263. One table of the design rules in support/graphics (rules.odin): the grid (8), the line (16), the gap, the margin and the title of a card, the three surfaces, the ten colors of the concepts, the colors of the title bars, and mix, shade, block, gradient. The boot display, the compositor, errors, locks, net and remote take all their rule colors from it; log and pages take the background, the text and the dim color. The colors of a program that are not colors of the rules stay in the program: docs/DESIGN.md, "State", lists them.
  264. 264. The desktop follows the rules (docs/DESIGN.md, section 9): the background of the rules with its grid of points, computed once; a bar of 32 pixels with the surface of a card; buttons of 24 pixels; the two menus as cards with a title, the caption of their source and a count; the menu of the programs in two columns (31 lines of 22 pixels filled the screen: 32 lines of 24 do not fit); a pointer of squares of 2 pixels; no shadow. The close button is a square of 24 pixels at the right end of the title bar, with the character x, not red: red is "failed". Its center is still 14 pixels from the right and 12 from the top, and the title bar is still 24 pixels: the machine test presses the same places. The places of new windows are not changed: the main scenario presses fixed places in them.
  265. 265. The gradient of the title bar (owner, 2026-11-10; reading of 2026-11-11: "the current blue" is the blue of the title bar, 0x3F7CC4). The title bar of the window that has the focus goes from 0x3F7CC4 at its left to 0x162C45 (36 %) at its right; the other title bars go from 0x2A3442 to 0x161D27, with a dim title: the gradient also says which window has the keyboard. The compositor computes the colors of one line once, for the columns that the frame shows, with a fixed-point step for each channel (no division for each pixel), then copies the line to the 24 lines. At its start it measures three forms of a title bar of 1024 pixels, 64 times each, and writes them on the log: one color 1.6 to 2.7 us, a mix for each pixel 65 to 87 us, one row of colors then copies 5.7 to 6.8 us (10 machines of one check). The gradient costs about 4 us for each window that a frame draws. At 8 bits for each channel the steps are 41 levels of red, 80 of green and 126 of blue over the width of a window: the picture shows no band, so there is no dithering.
  266. 266. graphics.copy does not call memcpy. The first measures showed a full frame of 9.1 ms with no window and 26.6 ms with 15 to 17 windows, and 21.5 ms for each frame of the move of a large window (boot, log, manual, paint), and one row of colors copied to 24 lines slower than a mix for each pixel (109 us against 65 us). The cause: in a program of this system, memcpy is the one of the freestanding Odin runtime (base:runtime), a loop of one byte, compiled with no optimization (each step reads and writes its counter in memory). graphics.copy copied each line of each window and of the background with it. copy_pixels now copies 16 bytes at a time; its store is volatile, so that the compiler does not make the loop a call of memcpy again. After: a full frame with one window 1.0 to 1.2 ms, with 15 to 17 windows 1.5 to 3.2 ms (mean 2.1 ms), a frame of the move of a large window 1.4 to 2.1 ms (mean 1.6 ms); the move of life 0.9 ms for each frame (3.4 ms before). The compositor writes these numbers on the log: "compositor: a full frame of 1024x768 with N windows in N us" (the first frame, and the frame after a window is put down) and "compositor: the move of NAME: N frames, N us per frame, N us the longest".
  267. 267. The launcher (owner, 2026-11-10, "as an experiment"; reading of 2026-11-11): a panel in the middle of the screen that the key super opens. The person types a part of a name; the list narrows at each key: a name matches if it contains the part, without the case of the letters, and the names that start with it come first (bar.filter, with five host tests). The arrows move the choice, Enter starts the program, Escape, the key super or a press of the pointer close it. With no match the list says so and Enter starts nothing. The key super is the left key with the logo (scan code E0 5B, display.KEY_SUPER): no program uses it, and the compositor sees each key before the program that has the focus, so it keeps this key, and each key while the launcher is open. The launcher lists bar.names, the table of the menu of the programs, and starts a program as the menu does: the record Start with the name to the root service. It has no other power. The menu of the programs stays. The log has "compositor: the launcher is open", "compositor: the launcher lists N of M for "p": ...", "compositor: the launcher chooses NAME", "compositor: the launcher starts NAME", "compositor: the launcher is closed by ...". The machine test has a step Keys: keys with no Enter.
  268. 268. support/launcher is now support/bar (one term for one concept: "launcher" names the panel of 267 now). The package has the names of the programs, the places of the bar and of its two menus, and the filter of the launcher. The picture of the menu of the programs is menu; launcher is the picture of the launcher. The picture desktop comes from the machine of the network, with four windows that the tool places; the main scenario keeps its picture as desktop_moved.
  269. 269. A strategy is a table of six functions over the data of a run queue (kernel/strategy.odin; owner, 2026-11-10: "study CFS, EEVDF and sched_ext", "change between the strategies"; the research note is docs/SCHEDULING.md). The idea of sched_ext: a strategy is a small set of callbacks. The six: ready (a thread becomes ready: new, woken, or back after its turn), pick, tick, stopping (a thread leaves its processor), wake_preempts, move. Each runs under the run lock of its queue (move under the two locks of a move). The lists, the locks, the balance between processors and the budgets stay generic: a strategy only orders the threads that a budget lets run. A strategy owns no memory: its state is fixed fields of the thread record (virtual, virtual_deadline, lag, placed) and of the run queue (strategy, weight, virtual), and zero is valid in each field for each strategy (P13).
  270. 270. The hot line. The fields that a pick, a wake and a tick read are the first 64 bytes of the thread record, which is aligned on 64 bytes; the run queue is one line of 64 bytes. A pick reads the line of the queue, one line for each thread of the queue, and the lines of the budgets of these threads (budget_spent). Measured in the machine test (three runs, Processor_Report.pick_cycles over the picks of the four cases): one pick takes 43 ns under the fair strategy, 81 to 86 ns under EEVDF (three passes over the list), 77 to 117 ns under round robin, whose turns of 10 ms put more of its picks on the path that takes a thread from another queue (queue_move, two run locks).
  271. 271. A scan, not a tree. A queue holds the ready threads of one processor: 0 to 12 in the cases of scheduler. A scan of 12 records of 64 bytes reads 768 bytes in order; a tree (CFS, EEVDF of Linux) needs a node for each thread, pointers and a rebalance at each insert. The measure above is the argument; a measure with long queues decides when to change.
  272. 272. Virtual times in weighted cycles, with integer arithmetic. A run of c cycles adds c * 1024 / weight; a charge is clamped to 2^32 cycles; two virtual times compare by the sign of their difference (they can wrap). Each queue has a queue clock that never goes back; a pick keeps each thread of the list within a bound under it (the sleeper credit for the fair strategy, the lag bound for EEVDF), so a thread whose budget had no time stays at a bounded distance. The average of EEVDF clamps each key to 2^44: the sum of 256 products fits in an i64. The argument is in docs/SCHEDULING.md.
  273. 273. The fair strategy (the idea of CFS): the least virtual time runs; a turn is 6 ms times the weight of the thread over the weight of the queue with it, at least 0.75 ms; a woken thread is at most 3 ms under the queue clock; it preempts when its virtual time is less by more than 1 ms, weighted; a move keeps the distance to the queue clock.
  274. 274. EEVDF: V is the weighted average of the virtual times of the threads of the list that a budget lets run and of the thread that runs on the processor of the queue. A thread is eligible when its virtual time is not above V; among the eligible threads, the earliest virtual deadline runs (the least virtual time when none is eligible). The deadline is the virtual time plus the request in weighted cycles; a thread that used its request gets a new deadline, and its turn ends if another thread waits. The lag (V minus the virtual time) is taken when the thread leaves its processor to wait, clamped to two requests (at least one tick), and the thread wakes at V minus its lag scaled by (W + w) / W, so that V does not move. A move keeps the lag. Not taken from Linux: the deferred dequeue (a sleeping thread stays in the queue so that its lag decays); here the clamp bounds the lag.
  275. 275. The time slice is the longest turn under each strategy. The first full check of the branch failed: "processors: at most 3 of 4 processors ran the program at the same time". Under EEVDF a thread alone in its queue never ended its turn, so its processor never passed through schedule_pick, where it takes a thread from a queue with more load. Now each strategy ends a turn after 10 ticks at most (strategy_tick), as round robin did; the check passed after it.
  276. 276. Wake preemption, also on another processor. With no idle processor, the waker asks the strategy of the home queue, under its run lock, if the woken thread preempts the thread that runs there. Yes: the waker ends the turn of its own thread when the home is its processor, else sets preempt in the processor record of the home and sends the interrupt that wakes an idle processor; the home ends the turn when the interrupt comes from a program. The deadline wakes of the tick of the first processor take the same path. Round robin has none.
  277. 277. A weight and a request at the creation of a thread (Thread_Create, values 5 and 6; system.thread_start(..., weight, request)). The weight is 1 to 1024, the request 1 to 100 000 microseconds; 0 is 1024 and 3000, so each program works as before. P20: a thread with no weight has the most; a program can give a thread less, never more. A weight is for a thread: a program with more threads has more share, as before (a share for each budget is a gap). The kernel refuses 1025 with Bad_Operation; its error record has the kind Bad_Value, the argument 5 (weight) and the limit 1024.
  278. 278. Strategy_Set (operation 27) and the right Schedule (10) on the machine. A new operation, as Machine_Stop: Inspect reads, it does not change the machine. The root service gives Schedule only to scheduler (grant Schedule). The operation gives the strategy before. System_Report has the strategy and the count of changes; Processor_Report has the strategy of the queue, the threads in it, the picks, the preemptions, the moves and the cycles of the picks.
  279. 279. The change while the machine runs. Strategy_Set writes the strategy of the machine, then converts each run queue under its run lock, one at a time: it clears the strategy fields of each thread whose home is the queue (in the list, on the processor, or waiting), then places each thread of the list with the new strategy. No list, state, home or processor changes: no thread is lost. A move between two queues of two strategies clears the thread. The zero values place each thread at the queue clock with a lag of zero: under a new rule the old history has no meaning, and each thread starts equal. The six interleavings (the tick, a wake on another processor, a move, a new thread, a stop, two changes at one time) are in ARCHITECTURE.md, "The change of strategy". The lock order holds: the program lock of the caller, then one run lock at a time.
  280. 280. EEVDF is the strategy at boot. The gap of choice 221 was a woken thread that waits for the end of a slice when each processor is busy. In the machine test (three runs), locks measured 304 964 to 394 204 wakes in one second alone and 127 906 to 181 062 while 2 lockers enter the kernel (186 to 2 690 before this step; 227 000 to 327 000 with only the desktop). The full check passed with EEVDF at boot.
  281. 281. The program scheduler (window of docs/DESIGN.md: cards with a title and a caption, rectangles on the grid of 8 pixels, the background with its grid of points, the color of the kernel for the cards and three shades of it for the three strategies, a bar from 125 to 78 % of its color, one large text: the strategy in use). The cards: the strategy in use with three tiles (a key 1, 2 or 3, or a click, changes it); the measures in three columns, with bars for the numbers and columns for the shares; the run queue of each processor (Processor_Report); the changes under load and the result. The window took 241 to 515 us to draw (three runs). The checks do not depend on the load of the host: no spinner below a sixteenth of its equal part, EEVDF above round robin in the same run, weight 1024 above weight 256 under the strategies with weights, each thread ran after the last change, each slot returned, no wake lost, the two refusals.
  282. 282. Measured (the machine test, QEMU with KVM, 4 processors, three runs, while other machine tests ran on the host). Two threads that wake each other, a spinner on each processor, wakes in one second: round robin 80 to 84, fair 461 732 to 498 800, EEVDF 313 601 to 332 954. A thread that sleeps 2 ms among 8 spinners, late by (middle; worst): round robin 18 513 to 19 343 us; 30 946 to 38 508 us. Fair 322 to 930 us; 733 to 1 807 us. EEVDF 343 to 916 us; 953 to 1 458 us. The tick is 1 ms, so a wake at a deadline is up to 1 ms late with no load. 8 spinners of one weight, the least and the most share: round robin 10.8 to 13.8 %, fair 10.6 to 13.4 %, EEVDF 10.6 to 13.7 % (12.5 % is the equal part). Their work in one second, in million increments: round robin 11 039 to 11 899, fair 9 182 to 10 342, EEVDF 11 627 to 12 637: the fair strategy was below the two others in each run, the cost of its shorter turns; EEVDF was not. 12 spinners with the weights 1, 2 and 4, the share of one spinner of each weight: round robin 7.6 to 9.2 % for each weight; fair 3.6 to 4.1, 7.4 to 7.6, 13.4 to 13.7 %; EEVDF 3.7 to 4.1, 7.4 to 7.8, 13.0 to 13.7 %. Turns ended by a wake: 0 under round robin, 89 454 to 123 176 under fair, 136 320 to 149 144 under EEVDF. 100 changes of strategy while 248 to 452 threads started and stopped, two threads woke each other and 8 spinners ran: in each run each thread ran after the last change and each slot returned. The test asserts no speed.
  283. 283. sched_ext, bounded. A strategy that a program supplies is not built: the kernel would run code that it did not verify, or a pick would wait for a round trip to a program (P5). A later step needs a form of strategy code that the kernel can check before it runs it (a verifier over the six callbacks and the fixed fields), a watchdog (a ready thread that does not run for a time ends the strategy), and the fallback, which Strategy_Set already is. docs/SCHEDULING.md, section 4.
  284. 284. Corrections after the review of the step (the coordinator and an independent read; no blocking fault). (1) Under the fair strategy a thread that waited kept its absolute virtual time: after the queue clock moved 2^63 (about 35 days of one thread of weight 1 alone in the queue) it looked far ahead and starved. Now it keeps its distance to the clock (lag, clamped to the credit below and 2^44 above) and wakes at the clock plus that distance, as EEVDF keeps its lag. A thread does not move while it waits; a thread that is back in the list is kept within the bound by the next pick. The correction is by argument: the kernel code does not run on the host, and the case needs 35 days. (2) A new thread under the fair strategy starts one least turn (weighted) after the queue clock, and so does each thread that a change of strategy places again (one rule for the two). Under EEVDF the lag 0 is enough: a new thread starts at the average, with a deadline one whole request away. (3) A waker that wakes an idle processor other than the home stores the home queue in it (look_first); the pick takes from that queue first. (4) At most one wake preemption of a processor in each millisecond by a thread of another program (P20: the time of the interrupt and of the pass through schedule is paid by the budget of the thread that runs there); a thread of the same program preempts with no limit. (5) A tick that ends a turn because a thread waits in the queue, also when its budget has no time, stays: it is a gap, with the reason. (6) The strategies start before the other processors (strategy_start in entry): the first full check after this move faulted at boot, because EEVDF read the record of a processor that was not yet in processors; EEVDF now skips a record that does not exist and returns at once for an empty queue. System_Report.strategy says that it gives the new strategy before each queue converted. (7) A wake interrupt that comes while the processor is idle ends no turn: interrupts are off in the kernel, so the interrupt comes either at the return to a program, where it ends the turn, or in the idle wait.
  285. 285. Measured after the corrections (one full check, master of 2026-11-11 with the desktop branch): two threads that wake each other, a spinner on each processor: round robin 65, fair 458 431, EEVDF 114 643 wakes in one second (about 320 000 in the three runs before; the limit of point 4 does not apply here, the threads are of one program; the cause of the fall is not found, and one run on a loaded host does not prove a change). The sleeper: late by 18 200, 202 and 1 005 us in the middle, 29 233, 3 031 and 7 275 us at worst. Weights 1, 2, 4: fair 3.8, 7.5, 13.5 %; EEVDF 4.3, 8.0, 12.5 %. locks: 648 874 wakes in one second alone, 290 136 while 2 lockers enter the kernel. One preemption by an interrupt costs 1 883 ns (fair) and 1 964 ns (EEVDF) in the kernel, from its handler to the next thread. One Strategy_Set with its kernel entry: 1 335 ns at least, 2 122 ns in the middle, 16 360 ns at worst over 100 changes; the window shows it. The window took 346 us to draw.
  286. 286. The two paths with no test before, tested. The peer drops the next datagrams of the remote service to the host when a step asks it (lose): the tool sends the request again, and monitor.served() before and after the step shows that the monitor ran the call one time. A step can send another hash (wrong_hash): the tool prints the hash that runs and the hash of the request. remote writes the sentence of a refusal one time, for the log and the window, and logs each repeated request (16 at most).
  287. 287. No log line for each call of the monitor. programs() and counts() wrote one line at each call; the check of the count of rows now calls monitor.count("programs") (rows_call). counts() has the scheduling strategy (its number, and its name as the unit) and its changes: 15 rows.
  288. 288. The compositor is a service, with its exports in programs/compositor/exports.odin and four lines in main.odin: the service starts after "compositor: ok", the wait list has the service, and the calls run after the input records, so that pointer() gives the place after the last move. The root service starts the compositor itself, not with start(), so it gives the service ring and the file there, and puts the compositor in its table of started programs, where a connection finds a service by its name; stop("compositor") stays refused.
  289. 289. pointer() is a Table of one row, not the Record that the coordinator asked: a Record result has no error record (section 4 of MODULE_CONTRACT.md), and the place of the pointer is a fact of the screen of the person (P20, line 6), so a caller that does not act for the person must get a refusal, not zeros. pointer() also has the menu that is open and the launcher; windows() has the focus and whether the keys go to the window (not while the launcher is open).
  290. 290. Where remote starts in the main scenario: after workers, by a blind launch. Not at the start: the compositor places each new window by the count of the windows before it, and the first part of the scenario presses at fixed places inside windows that it did not move; one more window before them moves each of them. Not in the root service of a test build: the system under test must be the system that ships. The persistence machines and the machine of a failed start do not run remote: their presses stay blind.
  291. 291. The tool keeps each module file and its hash for a client: a call read and hashed the file each time, 3.4 ms of the 3.7 ms of a confirmation of the pointer. Now a confirmation takes 257 us at least and 299 us in the middle (the last full check).
  292. 292. The log is a service: the program log holds the grant to read the log records, so it serves them, as the monitor serves the reports. records(after) gives rows of 256 bytes, the largest record of a description: the text keeps 168 of its 176 bytes. A caller reads from a number, and the first row says how many records the kernel wrote over in between.
  293. 293. The step Record waits for a log record through log.records: the program, the channel and the level must be equal, and the text must have a part. 11 checks moved, where the program and the level add to the text (the root service, the block services, disk, storage). The other checks stay .Wait on the serial port: it works before remote runs and when the network does not.
  294. 294. No Record argument from the host: no export that remote reaches takes a Record, so the parser has Integers and Texts only.
  295. 295. TCP is a library in the program (support/tcp, docs/NETWORK.md; property 5, its dated note, and the direction of the owner "UDP, TCP, QUIC, a server"). The state of each connection, the windows, the timers, the retransmissions, the congestion control and the reassembly are in the program, in records that it owns. The kernel did not change. The library is pure code over memory of the caller: no kernel operation, no allocation, so the kernel tool runs it on the host as the TCP of the peer, and odin test support/tcp has 15 tests (a link with loss, reorder and duplicates; the wrap of 2^32; the timer of RFC 6298 and its doubling to 60 s; a fast retransmit with no timeout; a zero window; RST and challenge ACKs; a close from the two sides at once; the error records).
  296. 296. A segment endpoint, not a new contract (contract/datagram). A TCP endpoint has the ring, the region, the commands and the error records of a datagram endpoint; its grant has the flag TCP, and a Send or a Receive carries a whole segment (20 to 1480 bytes). One mechanism for one concept: an endpoint carries IPv4 payloads of one protocol for one program. The grant is one value with two more flags (GRANT_TCP, GRANT_LISTEN); grant_unpack gives a record (datagram.Grant). remote and net read it.
  297. 297. Two forms of TCP grant (P20, lines 3, 4 and 11). A client grant names one remote; the network service gives a block of 64 local ports (a closed connection stays in TIME-WAIT, and a local port serves one connection to one remote at a time). A listen grant names one local port, which the root service chooses; the program answers only the admitted remotes: a remote that sent a segment to the port. The network service keeps 64 for each listen grant; the program releases one when its connection ends (Release), and a RST to a segment of no connection releases it too. A program with a listen grant cannot open a connection to a machine that did not come to it. A full table drops a segment from a new remote and counts it in the problems of the endpoint (kind Full). A listen port cannot be a port of another segment endpoint or of the blocks of client ports.
  298. 298. Where the checksums are computed and checked. The library computes the TCP checksum of each segment that it sends; the network service does not check it (a wrong checksum harms only the program that wrote it). The network service checks the TCP checksum of each segment that arrives, as it checks UDP (choice 198), because it reads the ports to choose the endpoint: a port that the wire damaged must not give a segment to another program. The coordinator proposed "the library", and this step changed it for that reason. A refused segment is a problem row: Too_Short, Bad_Header, Bad_Length, Bad_Checksum of the layer Tcp.
  299. 299. What the network service does on the data path for TCP: the same work as for UDP. It copies each segment one time (between the region and its frame), reads the ports in its own copy, checks them against the grant, writes the IPv4 header, and checks the TCP checksum of a segment that arrives. It keeps no sequence number and no window. So the dated note of property 5 is right about the place of the protocol, and not complete about the data path. docs/NETWORK.md, "The note of property 5", has the wording that the coordinator can record.
  300. 300. The interface of the library is data-oriented (owner, 2026-11-10, item 94 of HANDOFF.md). A connection is one record of 400 bytes (tcp.Connection); the caller gives the table of records and one block of memory for the buffers, and the buffers of a connection are at a place that its index gives. tcp.step takes all the segments that arrived, the time, the free places of the output and a table of events, and gives the deadline of the next timer: one call for a batch, no callback, no blocking call. An event is a record of 8 bytes: one for each connection with news. connect, write, read, close and abort change one record; an error is an errors.Error_Record (units Connections and Tcp_Ports, new). The program reads each field of its connections with no call: server draws them.
  301. 301. The rules of TCP that the library follows (docs/NETWORK.md has the table and the sources): RFC 9293 for the states, the windows and the RST; RFC 6528 for the initial sequence numbers (BLAKE3 of a secret and the four values, plus a counter of 4 us); RFC 6298 for the timer, with a least timeout of 200 ms, not 1 s (the round trips of the link are under 1 ms); RFC 5681 Reno, with the partial ACK of NewReno (RFC 6582) and Limited Transmit (RFC 3042); RFC 5961 for the RST, the SYN and the ACK out of the window. Measured on the host, 1 MiB with 5 % loss in each direction: 5 to 10 s with Reno and NewReno alone, 1.5 to 2.7 s with Limited Transmit (6 seeds). TIME-WAIT is 10 s (an MSL of 5 s for one link), not the 4 minutes of RFC 9293.
  302. 302. Faults that the tests found in the library before this commit, for the record. (1) recover of NewReno started at 0, not at the initial sequence number: with an initial number above 2^31, no fast retransmit could start. (2) A batch of segments after a hole got one ACK, not one for each segment (RFC 5681 4.2): with batches, the sender never saw three duplicate ACKs. The record now counts the ACKs that it owes. (3) Avoidance of the silly window could stop a sender with a small window and nothing in flight: the persist timer now ends that case too. (4) The congestion window grew with no end when the window of the remote limited the sender: it now stops at the largest window that the remote gave.
  303. 303. The secret of the initial sequence numbers comes from rdrand (tcp.secret_make), an instruction that a program runs with no handle; with no rdrand, from the cycle counter, which an attacker can guess. The log says which. The system has no random service: a gap.
  304. 304. A small HTTP/1.1 for the test (support/web): the head of a request and of a response, Content-Length, "Connection: close", the page /large whose bytes come from their offset, and FNV-1a of 64 bits. server, fetch and the kernel tool share it, so the three agree on the hash of 1 MiB: e7867887f2822325. Two host tests compare the hash with the one of core:hash.
  305. 305. The program server: one thread, a table of 32 connections with 64 KiB to send and 32 KiB to receive each (3 MiB of memory of the program), GET / (a page of its own counts: connections, requests, bytes, retransmissions, the time since its start, its kernel entries), GET /large, POST /sink. The time of an answer ends at the ACK of its last byte. A connection that this side closed is on the log when it enters TIME-WAIT, not 10 s later. Its window (docs/DESIGN.md, the colors of support/graphics/rules.odin): three cards (the listen grant with the count of open connections in the large size, the traffic with the count of answered requests, the measures), and the table of connections: the remote in the color of the machine, the state in the color of its phase (the boot program while it opens, the program while it runs, the kernel in the FIN states and TIME-WAIT), the bytes, the round trip, the congestion window as a bar whose width is its size against 64 KiB, the retransmissions and the probes; then the connections that ended, dim. One draw took 250 to 639 us.
  306. 306. The program fetch: a client grant of 10.0.2.100 port 80, eight cases with their answers, as net shows its cases. Two cases send a segment that the grant does not allow, written by hand (tcp.header_write), to show that the network service checks the copy and refuses it with the grant in the completion. fetch starts from the console (start("fetch")), not from the menu: it has a use only on the machine with libslirp.
  307. 307. The peer is a TCP client that the tool drives (tools/kerneltool/peer_tcp.odin, step Peer). It runs support/tcp in its thread, between frames: its socket waits 1 ms at most. It chooses the UDP port where QEMU receives the frames of the machine (local.port of -netdev dgram), so it can send the first SYN before the machine sends a frame. It reads each TCP frame of the machine with its own code, also the checksum. The cases change frames with a generator that each case seeds: each run drops, reorders and repeats the same frames. The cost: the library tests itself in these cases; the machine with libslirp tests it against another implementation.
  308. 308. The independent stack is libslirp, through QEMU user networking (-netdev user,restrict=on, a third network machine). hostfwd gives a free port of the loopback of the host to the port 80 of the machine; guestfwd gives 10.0.2.100 port 80 to a listener of the tool (a QEMU character device that connects when QEMU starts, so the listener reads with no time limit). The TCP of the machine talks to the one of libslirp; libslirp talks to the TCP of the host. A second network device on one machine was not needed: a second machine is one more start, and the frame service and the network service did not change. libslirp acknowledges the FIN of a connection of guestfwd and does not send its own: fetch stays in FIN-WAIT-2, and its case says it.
  309. 309. Measured (the machine test after the rebase on the step 46, QEMU with KVM, 4 processors, while other machine tests ran on the host; one run unless said). Between the peer and server: 1 MiB from the server in 52.9 ms (19.8 MB/s), 1 MiB to the server at 20.2 MB/s; with 5 % loss in each direction 0.8 and 0.7 MB/s (48 segments sent again; 2.2 and 0.8 MB/s in an earlier run); with 10 % reordered 16.6 and 15.3 MB/s; with 10 % repeated 16.1 and 14.9 MB/s; a zero window of 1.5 s: 4 probes. The handshake seen by server (SYN to the ACK of its SYN, ACK): 61 to 135 us at least. GET / answered and acknowledged in 157 to 224 us. With libslirp: the host fetched 1 MiB at 58.6 to 64.9 MB/s and sent 1 MiB at 57.6 to 61.0 MB/s (the clock of the host); server measured 92.9 to 98.4 MB/s out and 81.9 to 90.4 MB/s in; fetch measured the handshake in 261 to 266 us, 1 MiB in at 105.3 to 140.7 MB/s, 1 MiB out at 11.4 to 11.9 MB/s, and GET /hello in 42.6 to 43.5 ms, a cause that this step did not find (the tool answers at once; the time is in QEMU, libslirp or the wait of fetch). In the case of 16 connections, the answers were acknowledged 0.2 to 30 ms after their request, in the order of the connections: the peer runs one step for each frame. The window of a connection is 32 KiB (the receive buffer), so one connection cannot exceed 32 KiB for each round trip. The test asserts no speed.
  310. 310. Where the copy and the fill came from. With -no-crt, the Odin runtime (base/runtime/procs.odin) defines memcpy, memmove, memset and bzero with a strong linkage in the object of each module. memcpy and memmove are loops of one byte with the attributes noinline optnone in the LLVM IR: the code keeps its counter in memory. memset is a loop of one byte that LLVM makes a loop of 32 bytes. A symbol of another object cannot replace a strong symbol of the same link. The call sites in the linked modules, from the compiled code:
Modulememcpymemmovememset
compositor457
console353079
kernel14832
root22916
storage24721
  1. 311. The system has its own copy and fill (runtime/memory.asm): copy_bytes_without_overlap, copy_bytes_with_overlap, fill_bytes_with_value. The kernel tool links the file in each module and in the kernel (RUNTIME_MEMORY in tools/kerneltool/modules.odin), and in the boot program (the Win64 form), and builds them without -no-crt. No program and no kernel file changed for it. The compiler calls the three functions by the names of the C library: these names are aliases in a part of their own, and the code of the system uses the long names (owner, 2026-11-11: no short names such as memcpy).
  2. 312. The form of each size, by measure on the host (AMD Ryzen 7 5800X: ERMS and FSRM; the guest under KVM says the same, the compositor writes it), in ns for one call, the destination 3 bytes after an alignment:
Sizerep movsbloop of 64 bytesrep stosbfill loop of 64 bytes
51213.18.526.97.3
204827.531.037.128.1
409648.464.051.061.1
655369861265515978
524288101091113044388441
1 MiB43463211603859316855

The forms: under 16 bytes, general registers; 16 to 64 bytes, two or four moves of 16 bytes that overlap (all reads before all writes, so they also serve copy_bytes_with_overlap); then the loop of 64 bytes up to 2 KiB for the copy and 4 KiB for the fill; rep movsb and rep stosb up to 1 MiB; the loop again from 1 MiB. copy_bytes_with_overlap copies down in blocks of 16 bytes when the destination is after the source in the count, and uses rep movsb when it is before. The registers: xmm0 to xmm4, no AVX: fxsave does not save the upper halves of the ymm registers. The kernel uses these xmm registers in the file, as its compiled code already does: it saves the registers of a program at each entry (fxsave) and runs with the interrupts off, except in idle_wait. The rule of the direction flag is now in docs/MODULE_CONTRACT.md, section 4: clear at each call; the kernel clears it at syscall (MSR_SFMASK) and at each interrupt (cld).

  1. 313. Tests on the host: the tool assembles the file with -DHOST (the long names only) and odin test runtime links it: each size 0 to 300, each alignment 0 to 15 of the source and of the destination, the moves with 33 distances both ways (0, 1 to 17, 31, 63 to 65, half the count, the count and one more), the sizes where the form changes (2047 to 2049, 4095 to 4097, 1 MiB less 1 to 1 MiB and 13), and the fill with three values. A fault put on purpose (no write of the first 16 bytes of a move down) fails the tests.
  2. 314. graphics.copy is a plain copy again, and the compositor measures at its start, in the guest, ns for one call (8, 64, 512, 4096, 65536 bytes and a frame less 128 bytes, aligned): copy_bytes_without_overlap 1.5, 1.9, 9.9, 34.6, 923, 73765; the loop of 16 bytes of the step before 3.5, 19.6, 149, 1170, 19856, 956365; fill_bytes_with_value 2.8, 3.3, 7.6, 42.6, 502, 44292. Before and after on one master (9cf18a6 before, the same with this step after; the load of the host was 3 to 15 during the run after):
MeasureBeforeAfter
A title bar of 1024 pixels, one row then copies5.7 us1.7 us
A full frame, 12 windows or more1648 us (5 frames)1679 us (13 frames)
A frame of the move of life / boot269 / 1269 us250 / 1357 us
The first use of a page (frames)830 ns713 ns
Pages on first use in one second, 1 / 2 / 4 lockers (locks)1.24 / 2.32 / 3.47 million1.63 / 2.84 / 4.23 million
A call of the host to monitor.count, in the middle189 us169 us
The round trip of a datagram (net), in the middle83 us111 us
A burst of net, datagrams in one second95 14960 796
The boot program loads the kernel and the boot image19.5 / 20.1 ms22.6 / 23.2 ms

The last check after the rebase on bec7cfa (load 6 to 13) gave: the first use of a page 676 ns; 1.90 / 2.68 / 4.19 million pages on first use; the call of the host 170 us; the round trip of net 73 us; the burst 84 319. TCP, from server and the tool (before: the numbers of choice 309, 19.4 MB/s with the peer and 99 MB/s with libslirp): server received 1 MiB from the peer at 27.2, 1.3, 22.0 and 19.2 MB/s in one run; the host through libslirp fetched at 79.9 MB/s and sent at 90.8 MB/s. TCP did not gain a measurable part: it is bound by the network path, and one run gives values from 1.3 to 27.2 MB/s.

The frames do not change: a frame is bound by the writes to the framebuffer of QEMU (3 MiB in about 1.7 ms), not by the copy in memory. The kernel gains on the first use of a page (14 %, 31 % more pages in one second with one locker). The network and the boot program measured worse in the run after, under a load of the host 5 times higher; in the run of the step before on the old master with this change and a low load, the round trip was 67 us, the burst 94 443, the call 148 us and the load of the boot program 15.6 to 23.5 ms on 17 machines. These four measures are not conclusive on one run each.

  1. 315. The palettes of the programs: 20 programs take the colors that have a role of the rules from support/graphics; the others stay in the programs (docs/DESIGN.md, "State"). No layout changed. The places of new windows stay off the grid (see the gaps).
  2. 316. One developer tool, inspect, with the machine and the right Inspect, the boot image (the names of the modules and their exports) and a service ring (grants_of). It reads reports and changes nothing. It does not read a byte of the memory of another program: the reports give counts, places, states and the stack trace. That stronger right (P20, line 2) is not added; the hex view shows the memory of inspect itself (docs/TOOLS.md).
  3. 317. Four reports of the kernel for a developer tool, each read-only, through Inspect (contract/operation/operation.odin lines 370 to 445; kernel/report.odin lines 103 to 225; the cases in kernel/enter.odin line 715 and 746; three buffers of the processor record, kernel/processor.odin line 161; the sizes, kernel/diagnose.odin line 563). Program_Mappings: each mapping of a program with the pages that have a frame (read from the page table, as present_pages does), its memory object and flags. Threads: the state, the processor, the home queue and the fields of the strategy of each thread, read with no run lock (each field whole). Stop: the stop report of a slot. Pages: which of 64 pages from an address have a frame, so that a reader of bytes gives no frame by its read; slot 0 is the program that asks. The reports are built in the processor record under the control lock and the program lock of the program, then copied to the caller with no lock of another program. kernel/memory.odin and kernel/paging.odin are not changed: the reports call page_entry with no creation.
  4. 318. The kind of a mapping comes from facts: the module record that holds its address (code with Execute, data with Write, constants else), Device, Thread_Area, Shared (another handle or mapping names the memory object), else memory of the program. The kernel does not know a stack: a stack is memory of the program.
  5. 319. The band of an address space (support/addresses, with unit tests on the host): each mapping has 4 pixels and a part of the rest in proportion to its pages; a gap between two mappings is a folded gap of 16 pixels with lines at an angle (DESIGN.md, section 2). An address is module + offset in the module record that holds it, and a frame of a stack trace adds the near export: the export with the highest address at or below it.
  6. 320. Live samples, and their cost. A sample each 250 ms: the program report of each of the 64 slots (the list) and five reports of the chosen program, 68 kernel entries in all. In the last full check, after the count of the present pages in each mapping (324): 33 to 62 us (a sample of the compositor, 55 mappings and 8319 pages: 48 us; of threads, 18 mappings: 35 us); before it, with the walk of the page table: 95 to 143 us, and 1.5 ms for the first sample with cold caches. A value that changed since the sample before is drawn in the color of the boot program for one sample. The kernel time of the mappings report grows with the mapped pages of the program: it reads one entry of the page table for each page.
  7. 321. The same facts as exports: inspect.mappings(program), inspect.threads(program), inspect.modules(program), Tables with error records, for a caller that acts for the person; callable_of("remote") has inspect.
  8. 322. errors keeps the program fault that stopped until the next run of its case: the slot of a program that stopped ends with its last handle, and the developer tool shows its stop report while it lives.
  9. 323. The test: the step Agree compares two tools on two reports of the kernel (monitor.programs() and inspect.mappings(), inspect.threads()); the scenario starts monitor again before it, because the scenario revoked its machine grant before. kerneltool test main runs the main machine only.
  10. 324. Corrections after an independent read of the kernel part. (F1) Pages answers for the program that asks only: about another program it was a side channel, page by page; an index that is not 0 gives Bad_Operation with the error record Bad_Value of the argument 3, and an address whose 64 pages pass the end of the user half is refused; the case takes no control lock (the program lock of the caller is held). (W1) The pages with a frame are a count of each mapping (Mapping.present_page_count, kernel/program.odin): map_region sets it, page_give adds 1 when an entry becomes present, unmap ends it, each under the program lock that the path holds; present_pages of Program_Report and the mappings report read it in place of a walk of the page table with no bound, under the control lock. The test build checks it at each unmap. (W2) The stop report of a free slot is zero, not the stop of its old program. (W4) Program_Report, Module_Report, Stop_Report and the two new reports have the serial of the program; inspect drops a sample of two programs and chooses by slot and serial. (W5) The stop report through Inspect keeps only the frames in a module record of the program, and the address of the fault only in the first page or a module record (docs/TOOLS.md). (N2) Each export of inspect checks that the caller acts for the person; the network machine shows the three refusals after revoke("remote", "person"). The click on the list is a step of the test.
  11. 379. The color of an error record in the window of remote: an answer whose export gave an error record is drawn in the amber of the rules (COLOR_BOOT_PROGRAM), the color that the program log gives to the level Warning: the call ran and the service gave its failure as a value that the caller can act on, so it is not the color "failed" and not the green of a result. A refusal of remote itself ("refused by remote") and of the service keep the color "failed": no call ran.
  12. 380. The refusals of Pages are cases of errors, with the sentences of their error records: t "Inspect: the argument index has the value 1, which the call refuses" (the pages of another program), u "Inspect: the argument address has the value 140737488351232, which the call refuses; it takes at most 140737488093184" (the 64 pages from the first page pass the end of the user half). The kernel explains them in value_explain, the path of Bad_Operation. errors has a second page of cases (Tab): its window keeps the height of 19 cases.
  13. 381. log.records cuts no byte. A row of a description has 256 bytes at most, so the text of a row has 160 bytes: a log record of up to 176 bytes gives two rows with one number, and a row says continues = 1 when its text goes on in the next row (the rest of the record, or the next record when the kernel split the text, Log_Record.more). The step Record joins the rows of one text: the line of errors under contention, 2 log records, comes back as one text from 3 rows.
  14. 382. A disk that loses what was not flushed, served by the kernel tool (network_block_device.odin): QEMU reaches the data disk through the Network Block Device protocol (fixed newstyle handshake, simple replies, read, write, flush and the flag FUA; about 300 lines with core:net). The tool keeps two images: what the machine reads (each write), and what is durable (each write before the last flush, and each FUA write), and the writes after the last flush in order. Chosen over blkdebug of QEMU: a rule of blkdebug can drop writes, but the tool would not know which writes the machine made; with NBD the tool is the model of a real disk and needs nothing of QEMU but its NBD client.
  15. 383. The loss of power at the write of a root. A loss at a moment of the clock nearly never falls between a write and its flush (each commit is two writes and two flushes, QEMU joins the blocks of one write). So the disk loses its power at the first write of a root (blocks 0 and 1) after a seeded count of writes: the hardest moment, when the blocks of the commit are durable and its root is not. A loss at a write of data blocks is a subset: those blocks are free for the durable root.
  16. 384. A search, with no machine for each image. The open of a data disk (the choice of the root, the walk, the check of each block) moves from the engine to support/storage_mount, which the storage service and the tool both run. The tool checks each image: it opens; no block has a wrong check, no block is named two times, the count of blocks of each store is what its table names (two new counts of the open; the service writes them on the log if they are not 0); the objects are those of the durable image or those of the image with every write, byte for byte, never a mix. It found no fault of the engine. The engine asks the flushes at the right moments (its blocks, then its root), and the block service passes each one to the device (VIRTIO_BLK_T_FLUSH): the disk received two flushes for each durable commit.
  17. 385. A ring completion that fails holds an error record in its values (errors.completion_record), the form of the completion of the kernel: block, frame, datagram, the control ring of the network service and the root ring. It replaces the values of each status of choice 199. A remote and its port are one value (errors.endpoint_pack, the unit Endpoint). New kinds: No_Answer, Unsupported, Link_Down, Busy, Out_Of_Range, Reserved; new units: Requests, Endpoint, Region, Root_Commands, Services, Grants, Display, Terminal. The check of a data region of a device ring is one procedure (system.region_refusal) for the block service and the frame service.
  18. 386. The root service finds the cause of a refusal after it, as kernel/diagnose.odin does (refusal): the start, stop and connect procedures keep their status, and one procedure reads the state again. A cause that the state does not show now is Refused, with the status. The console writes the sentence of the record in place of a text that it guessed from the status (it said "no program has the name" for each refusal of Stop). Replace keeps the texts of the console for its statuses (a chain, a full table, a module that replaces itself), which its contract documents.
  19. 387. A wrapper writes the record of the kernel operation that failed in it, or its own (system.wrapper_error_text): a record with an operation is the kernel's, a record with the operation 0 names an argument of the wrapper (display.OPEN_ARGUMENTS). display.open now refuses a size of 0 or above 2048 and a title of more than 31 bytes, which it cut before; no program of the system passes one.
  20. 388. An export that refuses must give a negative result for it: paint.line and paint.write give an Integer now (the lines drawn, the bytes of the text), and tally.add keeps the count, which it never takes below 0: an amount that would is refused (Too_Small). Before, a wrong color was taken modulo 6, a place off the paper was dropped with no answer, and a long note was cut.
  21. 389. The sentence of Bad_Value says "it takes at most N": "1 to N" was wrong for the arguments whose first value is 0 (an index of a connection, an offset, a color).
  22. 325. The close button is the concept "stop a program" (owner, 2026-11-11: "et ce serait bien que les x de fermeture soit rouge, riche"; the coordinator chose the nearest form that keeps "one color is one concept"). A deep red COLOR_STOP_PROGRAM (0xB82232), not the coral of "failed": a block from 118 % at the top to 82 % at the bottom, a top line at 150 %, the x of the font in white; brighter (0xE0343F) when the pointer is over it, which the compositor draws again at each change; quiet (0x4E2229, a dim pink x) on a window that is not in front. A cross of squares of 2 pixels was tried beside the character: its two lines do not meet on one square at the centre; the character stays (the two forms are in the devlog of 2026-11-11, close_preview). The center of the button does not move: the machine test presses the same place. No program has a button that stops another program: rings shows the state "stopped" of a node, a state and not a button. flake.nix names runtime/ (the file set of the flake did not see runtime/memory.asm).
  23. 326. The interface package (support/interface, docs/INTERFACE.md). What the programs draw by hand, counted on cbe3c0e: 34 programs, 591 lines with a drawing call, 21 programs with cards, 5 private copies of card, 5 of large text, 15 programs with bars, 8 with tables, 3 lists with a choice, 2 text inputs; each program draws its whole surface for each change. The model: immediate mode over draw tables in the memory of the caller (P14): each frame the program describes its window again; each element appends draw records of 40 bytes (format version 1: a rectangle, a vertical or a horizontal gradient, a run of text, a clip, the grid of points) and gives an Element_Result as a value. No allocation, no callback, no global state.
  24. 327. The damage by difference: the package compares the table of the frame with the table of the frame before, record by record at one index (the bytes of the records, the bytes of their texts); a record that differs damages its old and its new rectangle. A record added in the middle damages each record after it: too much, never too little. The host test damage_is_never_too_small draws a frame in its damage over the frame before and compares it, pixel for pixel, with the frame drawn whole (the idea of the verify mode of gui_toolkit). display.present_rectangle gives the compositor the rectangle that changed: before, Present damaged the whole surface (a gap of this file). A surface with two pictures is drawn whole.
  25. 328. The cost of the difference, measured by interface_elements on its own window of 68 records, the mean of 100 rounds: nothing changed, 0 rectangles in 0.8 to 0.9 us; one number changed, 3 rectangles in 0.8 us; everything moved, 1 rectangle of 96 % of the window in 1.8 us. The whole frame drawn: 366 to 411 us; the next frame in its damage: 89 to 120 us. The compositor finds its damage by hand at no cost of comparison; the comparison costs about 13 ns for each record, under 1 % of the drawing that it saves.
  26. 329. Layout in one pass: an Area on the grid; a column gives areas down; a row splits an area into columns whose sizes the caller gives before it draws (Pixels, Characters, Share). No tree and no solver: the windows of the system have fixed sizes.
  27. 330. Input and focus: the display events of a frame make an Input; an element that takes input has an identity (the 64-bit FNV-1a hash of a text); the interface state keeps the focus, the element under the pointer, the pressed element and the order of Tab of the last frame. Tab and Shift+Tab move the focus, Enter and Space press a button, Enter activates a list or a text input, up and down move a choice, a text input takes the characters and Backspace. 12 host tests: the layout, the difference, the text measure, the keyboard, the pointer, the text input and the list.
  28. 331. Three users, each with no direct drawing call left. clock (32 drawing calls to 0): a card of the rules around the same facts (816 x 312 pixels); one picture in 11.6 us, the mean of 10, against 109.2 us before (37 records, 1 damage rectangle: the time since boot; build 2.7 us, difference 1.3 us, draw 4.9 us). The window of remote (31 calls to 0): the same three cards; the old view of the first card cut the legend of its bar, which the package showed: the card is 16 pixels higher, the window 528. One picture in 160 us against 173 us before (105 records, 1 damage rectangle: most of its values change at each picture). The launcher of the compositor (48 lines of launcher_draw to 24 lines of launcher_build): a draw table damaged by difference, given to the damage list of the compositor; its text input takes the characters and Enter through the package. A frame while it is open: 182 to 241 us against 375.7 us before.
  29. 332. The program interface_elements: each element live (a button, a list with a choice, a text input, two bars, lines of a label and a value, a table, the large number of a card), the draw table of the last frame (records, bytes of text, damage rectangles, the time of the build, of the difference and of the drawing), and a button that shows the damage of each frame as outlines (records of the next frame, so a damage that stays does not damage again). The scenario reaches each element with the keyboard: Tab and Enter press the button, Tab and down move the list, Tab and "hello" fill the text input. Pictures: interface_elements, interface_damage.
  30. 333. A vector font, as research (docs/INTERFACE.md, section 9; Spleen 8x16 stays, owner, 2026-11-10). The Slug patent (US 10,373,352) was disclaimed into the public domain on 2026-03-17 by its author, and reference shaders are free to use with credit (verified on 2026-11-11, sources in the note). The prototype support/vector_font: a parser of a subset of TrueType that checks each read (host tests with files cut at 345 lengths and 64 files with 64 changed bytes: no fault), a rasterizer with exact area coverage, a subpixel position and color stripes with the filter 8, 77, 86, 77, 8. It uses f32, against section 7 of DESIGN.md: a version for the system needs fixed point. The program vector_glyphs shows one text in Spleen 8x16 and in DejaVu Sans Mono (Bitstream Vera license, programs/vector_glyphs/FONT_LICENSE; 343 096 bytes in the boot image) at 13, 16 and 32 pixels. One glyph: Spleen 32 ns; 13 px from its outline 2939 ns in grayscale, 2396 ns on a whole pixel, 5502 ns with color stripes: about 90 times a bitmap glyph, with no cache. A cache of the coverage of each glyph, size and position is not built and not measured. Picture: vector_glyphs.
  31. 334. The admitted remotes belong to the network service, and admission is on a SYN (finding B1 of the hostile read of 295 to 309). Before, any segment admitted its sender, and only some ends sent a Release: a host that sent a bare RST from 64 ports closed the listen port until a restart. Now the network service admits a remote only on a SYN with no ACK and no RST, only when a Receive of the endpoint waits for it, and never from 0, a broadcast or multicast address, or its own address (network.source_valid). Each admitted place keeps the time of its last segment, in or out; when the table is full, the oldest place with no segment for 60 s serves again (ADMITTED_IDLE): a program that forgets a Release loses only places with no traffic. 60 s is more than each timer of support/tcp and of server (TIME-WAIT 10 s, FIN-WAIT-2 20 s, the limits of 10 s; a connection in retransmission sends a segment at least each 60 s, the most of its timer). The program side stays exact too: server releases the remote of each segment whose four values have no record after a step (tcp.find is public now), a passive open that a RST ends gives the event Reset, and a release waits until the Sends of its pass are in the ring and its record has no RST to send (a Release before a RST made the service refuse the RST: found by the case slow). A segment of a remote that sent no SYN does not reach the program: the RST of RFC 9293 for it does not go, as behind a firewall. Tests: admitted_remotes, sources_that_are_refused (host); the peer case leak (64 ports each of a bare RST, a bare ACK, a SYN then a RST, a SYN with an option that the library refuses; then a page, 106 to 107 ms after the last of the 320 segments); the case stray now checks that no RST comes.
  32. 335. A connection that makes no progress ends (finding B2). server: 10 s with no byte of a request on an open connection: a close; a head that is not whole after 10 s: 408 and a close; a body under 4096 bytes in one second after 10 s: a RST; 10 s with no byte read and no byte acknowledged while a request is in progress (a zero window for ever): a RST; a Content-Length over 16 MiB: 413; a FIN while a request is not whole: a close at once. 10 s is long for a request on a link where a round trip is under 1 ms, and short against 48 records. server passes its loop at least each second while a connection is open (with no segment and no timer of TCP, it waited with no deadline: found by the case slow). The library: a FIN-WAIT-2 timer of 20 s (a RST at its end); Table.remote_limit, the open connections of one remote address (24 of 48 in server); a SYN to a full table ends the oldest record in TIME-WAIT and is dropped (the remote sends it again in 1 s), else a RST. Test: the peer case slow, 32 connections of one host in 7 kinds (a handshake then nothing, half a head, a trickle body, a refused request then no FIN, half a head then a FIN, a zero window for ever, a Content-Length of 999 999 999 999): 24 accepted, 8 refused with a RST, the server ended the last one after 11.1 s, then a page came after 11.1 s. Host tests: fin_wait_2_ends, remote_limit, syn_to_a_full_table.
  33. 336. Faults of the library from the hostile read (each with a host test that failed before the correction; support/tcp/hostile_test.odin). F1: a RST is checked at the sequence that came, not after the cut of the bytes before RCV.NXT (RFC 5961 3.2): a RST with data was 1460 times easier to place. F2: 8 probes of a zero window with no segment of the remote after them end the connection (Timed_Out). F4: a passive open ends after 3 timeouts of its SYN, ACK (15 s), not 8 (243 s). W1: the ACKs that answer segments out of the window and the challenge ACKs are 10 in one second at most for each connection (one limit for all connections was CVE-2016-5696). W2: a timeout sets recover to the highest sequence sent (RFC 6582 3.2, step 4), and outside recovery recover follows the acknowledged edge, so that a fast retransmit still starts after 2^31 bytes. W3: the SYN counts in the acknowledged bytes once, by a field (syn_acknowledged), not when SND.UNA is the initial sequence again after 2^32 bytes. W4: a FIN is taken only in ESTABLISHED, FIN-WAIT-1 and FIN-WAIT-2; a FIN again in TIME-WAIT restarts its timer.
  34. 337. The notes of the read, corrected: the send window takes no update from an ACK before SND.UNA; the inflation of fast recovery stops 3 segments over the largest window of the remote; syn_sent moves send_next to the ACK when a timeout had set it back; no round trip sample from a SYN or a SYN, ACK that went two times (Karn); Endpoint_Grant takes only a grant of the same form (UDP or TCP, client or listen, the same listen port), because the local ports and their check come from Endpoint_Add; rdrand that gives all ones or two equal values in four is refused (the fault of some AMD processors), and the window of server shows "ISN secret: weak" in the color "failed" when the secret comes from the cycle counter. Two faults that the new tests found: a simultaneous open never ended (SYN-RECEIVED sent no ACK for a segment out of the window), and a passive open that a RST ended gave no event. Tests that the read found missing, now present: loss, reorder and duplicates at the wrap of 2^32; a RST with data; the 8 ranges full and one segment over several ranges; a simultaneous open; a SYN to a full table; an option of length 0 and an option past the header. tcp.Connection is 424 bytes. 33 host tests.
  35. 338. Commands that the ring does not take wait for the next pass (finding F3): server and fetch put each command through command_push_or_hold; a full ring keeps it in a table of the program, which the next pass sends first. A Send, a Receive or a Release is never lost, so no place and no admitted remote leaks.
  36. 339. The 43 ms of GET /hello through libslirp were a fault of fetch. QEMU now writes each frame of the machine with libslirp, with its time (filter-dump), and the tool prints the frames of GET /hello after the run. The frames showed the answer 1.9 ms after the request; a time in fetch showed the first segment with data 2.0 ms after the start. The exchange loop of fetch checked "the answer is whole" only after its next pump, and pump waited for the next timer: the delayed ACK, 40 ms. Now GET /hello takes 2.7 ms; the 1.9 ms that remain are in libslirp and its character device.
  37. 340. The spread of the throughput. A run of the controlled machine has the case of 5 % loss (0.6 to 2.2 MB/s by design) beside the clean cases. On a quiet host (a load of 1.9 to 2.1) the peer received 1 MiB in 16.3 to 17.9 ms from its first byte to its last one (58.5 to 64.2 MB/s, 4 rounds), while server wrote 13.9 to 18.1 MB/s: its time ended at the ACK of its last byte, which the peer delays up to 40 ms (a delayed ACK at the end of the data). The measure was wrong, not the library: server now writes the time when the last byte left the program, and the time of its ACK beside it. The peer writes the load of the host (/proc/loadavg) beside each round.
  38. 341. A simple HTTP/1.1 server (owner, 2026-11-11: "faudra faire un simple server http 1.1 après"; RFC 9110, RFC 9112). server keeps one thread and one table; its state is records of fixed size, and it allocates nothing. support/web.request_read reads a request head as hostile input: each length is checked before a byte is read; a head has 8192 bytes at most (431), 64 fields (431), a target of 1024 bytes (414), a Content-Length of 15 digits (413 over it, 400 when it is not digits or two values differ); a method that it does not know is 501, a transfer coding (also chunked) 501, a version other than HTTP/1.0 and 1.1 505, a folded line, a space before a colon or a control byte 400, HTTP/1.1 with no Host or two Host fields 400, a target that does not start with "/" 400. Host tests: each bound on both sides, each problem, 50 000 heads with random damage. Connections stay open after an answer (HTTP/1.1), unless "Connection: close", or HTTP/1.0 with no keep-alive. The bytes after a head stay in the input of the connection: they are the body, or the next request (pipelining); the answers go in the order of the requests, one at a time for each connection. HEAD gives the head of GET with no body. 405 has an Allow field; PUT and DELETE are refused: the network is not authenticated. A request with a body that the server does not read gets its answer and the end of the connection. No Date field: the system has no clock of the day, and RFC 9110 6.6.1 says that a server with no clock sends none. The content type comes from the end of a name (a table of 9).
  39. 342. What server serves, by its grants only (P20): its counts as HTML (/) and JSON (/counts.json), /large and POST /sink for the tests, and the pages of its private store (P9, grant Store), by name. The server reads its pages into its memory at its start, and serves them from there: a request never waits for the storage service. A person changes them from the console through the exports of the server (P19, grant Serve): server.put(name, text), server.remove(name), server.pages() (a Table), server.served() (a Record). put and remove change the store first, and only for a caller that acts for the person; a name has 1 to 39 bytes of letters, digits, ".", "-" and "_"; a page has 60 KiB at most (the call region has 64 KiB); 16 pages. The store keeps an object pages.index with their names. No shared folder (P8).
  40. 343. The window of server has two tables: the connections (the state in the color of its phase, the bytes, the round trip, the congestion window as a bar) and the last 18 requests (the method, the target, the status in the color of its class, the bytes, the time from the first byte of the head to the last byte of the answer in the send buffer), with the answers by class of status as bars, and the measures. One draw took 593 to 830 us. The coordinator offered support/interface (step 51) for this window; it was finished before that package came, so it keeps its own drawing with the colors of support/graphics/rules.odin. Each request is a log record in the channel requests, 100 in one second at most (the log limit of the program is 1000): the others are counted, not written.
  41. 344. A SYN to a full table takes the record of the oldest TIME-WAIT at once (RFC 6191 takes a SYN in TIME-WAIT too). The remote of that record goes to Table.reclaimed_remotes, which the program reads after each step to release it (no Closed event comes for it). Before, the SYN was dropped and came again 1 s later: with "Connection: close" each connection held a record for 10 s, and a new connection for each request gave 4 requests in one second with 48 records; 239 to 250 now.
  42. 345. Faults that the tests of this part found and that are corrected: the peer lost the bytes of a first read past its head buffer of 2048 bytes (1 MiB came short by 5252 bytes when a head and 5 segments came in one read); a release and the RST of its connection in the wrong order, and no wake for the limits (choice 335).
  43. 346. Measured (the last runs of kerneltool test tcp, the load of the host 7.9 to 8.7, at the limit of 8 that the coordinator set: these numbers are high or low by the load). From the TCP of the host through libslirp: 1000 requests on one connection at 2311 to 3230 requests in one second (the clock of the host); server measured 2322 to 3249; 300 requests with a new connection each at 239 to 250 requests in one second; / in 5.5 to 6.4 ms with the connection, /large at 36.7 to 66.9 MB/s. The peer received 1 MiB in 19.3 to 19.8 ms from its first byte to its last one (53.1 to 54.4 MB/s); 58.5 to 64.2 MB/s with a load of 1.9 to 2.1 (choice 340). fetch: GET /hello in 2.7 to 3.1 ms, of which 1.9 to 2.8 ms are libslirp (the frames).
  44. 372. Bytes must move, in both directions, after the close too (the read, F3 and W6). A body with no byte in 10 s ends. A body, and the bytes that this server sends, move 4096 bytes in one second over each period of 10 s, or the connection ends with a RST: a rate over each period, not an average from the start (1 MiB at once then nothing held a record 68 minutes; one byte acknowledged each 9 s held an answer for days). "Sending" is an answer in progress, or bytes in the send buffer that the remote did not acknowledge: a page of up to 60 KiB fits in the send buffer, and its request ends at once. The limits run in FIN-WAIT-1, CLOSING and LAST-ACK too. No byte acknowledged in 10 s and a rate under the least one are one fact with one reason on the log ("an answer under 4096 bytes in one second"): which rule sees it first depends on when the first bytes went. Not a limit of the whole answer (bytes / 4096 + 10 s, the form that the read offered): the rate over each period gives the same bound and one rule for both directions.
  45. 373. An answer of a page keeps the generation of its place (the read, B2). Each put and remove gives the place a new generation; answer_write compares it before each copy, and a page that changed ends the connection with a RST: the remote has a prefix of the old page and a RST, never bytes of two pages under one Content-Length. The form that the read named the stronger one; put and remove do not walk the sessions.
  46. 374. The framing of a request (the read, F4 and W7). A request with a body that the route does not take ends the connection after its answer, with each status: its body is not read as the next request (RFC 9112 6.3; with a proxy before the server, request smuggling). A close, not a read of the body that drops it: no new state, and a GET with a body has no meaning (RFC 9110 9.3.1). After the FIN of the remote, the whole requests in the input get their answers in order, then the close.
  47. 375. The store first, then what is served (the read, F5, W8, W9). put writes the store from one staging buffer of 60 KiB + 1, and copies into the page memory only when the store took it; remove calls the store first; a new page whose index the store refuses goes out again. A refused call leaves the page that is served as it was, and says so on the log. The start reads the store as hostile input: web.page_index_entry refuses an entry with no end, a name that breaks the rules, a repeated name; a page over 60 KiB is refused; / escapes each name (web.html_text_escape). A page that changes keeps its count of answers: it is the same name for the person.
  48. 376. Bounds that are proven, and a log budget (the read, W10, W11, W12). The held commands of server have the bound PLACE_COUNT + RELEASE_WAITING_CAPACITY: a Send or a Receive names one place, a place is in one command at most, and a Release is held only while fewer than RELEASE_WAITING_CAPACITY are; passing the bound stops the program (a check, not a loss). fetch has the bound PLACE_COUNT. The lines of the ends of connections have 50 in one second, and one line counts the others. An admitted remote serves again after more than 120 s with no segment: twice RTO_MOST, the longest gap between two probes of a zero window.
  49. 377. The forms of a request that a server must take (the read, notes). The absolute form of a target (RFC 9112 3.2.2: MUST): the scheme http or https and the authority go, the path stays. HTTP/1.2 is read as HTTP/1.1 (RFC 9110 2.5). Two empty lines before the request line are ignored (RFC 9112 2.2), a third is a bad request line. HTTP/1.0 with keep-alive gets Connection: keep-alive. Each answer says X-Content-Type-Options: nosniff. The origin rule: the pages of the store, / and /counts.json share one origin, and a page of the store runs script on it by design: the origin holds no secret, no cookie and no authority (the counts are public, and a change of a page needs the console). The search for the end of a head starts where the last one stopped.
  50. 378. No check depends on the load of the host (the coordinator, 2026-11-11: the flake of master failed at a load of 28 on the 408 after half a head). The timers of server count on the clock of the machine, which a loaded host slows. A wait of the tool for an answer has a bound of 120 s (HTTP_HOST_WAIT_LIMIT), a peer case 180 s (CASE_TIME_LIMIT): bounds that end a test that hangs, not measures. The case of the page that changes waits for the step Peer_Result, not for a time. Changed: the HTTP case "errors" (408), "body-not-taken" and "requests-then-fin" (5 s before), forward_fetch (20 s before), each peer case (60 s before), and "page changed during its answer" (no time at all). Looked at and kept: the waits of the log (STEP_TIMEOUT, 20 s): each one that follows a timer of the server comes after a peer case or an HTTP case that already waited for that event; slow, leak, "zero window" and the FIN-WAIT-2 timer of the library compare no time.
  51. 347. The fall of the wake rate of EEVDF has no cause in the corrections of choice 284 (part A of the next step after the merge). Choice 285 measured 114 643 wakes in one second for two threads that wake each other under EEVDF, against 313 601 to 332 954 in the three runs of choice 282; each was one run of the full machine test, on two different trees of master (the services of the call from the host and the desktop came between) and on a loaded host. The new command kerneltool measure scheduler N runs scheduler N times on a machine with only the desktop. With the kernel of this step (5 runs, load average of the host 2 to 5): round robin 397 to 400, fair 251 554 to 342 127, EEVDF 127 559 to 178 706 wakes in one second. With the kernel before the corrections (3 runs, the same program and tool): round robin 399 to 400, fair 205 561 to 272 386, EEVDF 147 666 to 173 462. The two ranges of EEVDF overlap: no correction (the hint, the limit, the early return, the check of a record, the move of strategy_start, the measure of a preemption) lowers it, so the step did not remove them one at a time. In these runs almost each turn that a wake ends is on the processor of the waker (EEVDF: 50 211 to 71 034 in the 400 ms of the case, by an interrupt 70 to 95): the two threads and a spinner share one processor; the limit of choice 284 does not apply (one program). EEVDF stays below the fair strategy because its wake path reads the queue three times (the place of the woken thread, the preemption, the lag at the wait) and its pick three times, where the fair strategy compares two numbers. The numbers of the full machine test depend on which programs share the processor of the two threads; the test asserts no speed, only that EEVDF wakes more often than round robin in the same run (more than 100 000 against 400 here). The worst time of one Strategy_Set was 3.9 ms and 20 ms in two runs of five (16 to 17 us in the others): the host took the processor of the machine; the middle was 1.7 to 1.8 us in each run.
  52. 348. A frame cache for each processor (kernel/memory.odin; the next form named in the gap of the frame lock, choice 221). Each processor keeps a stack of at most 256 free frames in its processor record, under its own cache lock: a page on first use takes its frame there, with no frame lock; an empty stack takes one frame and a batch of 128 in one section of the frame lock; the end of a memory object puts its frames on the stack of its processor (the frame lock is held there for the budget anyway); a full stack gives a batch of 128 back. A frame of a stack keeps its bit in frame_bits and is not in free_frames.
  53. 349. A lock for each stack, not a drain by interrupt. The brief offered two ways to take the frames of the other stacks back: an interrupt to each processor, as processor_sync, or a lock for each stack. A stack with no lock and interrupts off can be read and emptied only by its processor: a taker that holds the frame lock and waits for the other processors to empty their stacks needs them to take the frame lock, which it holds; and the report could not read the stacks. With a cache lock (kind Cache, after Frame in the lock order), a drain takes each cache lock in turn under the frame lock and always gets it, since no processor waits for the frame lock while it holds a cache lock. The cost: one uncontended lock pair for each page on first use and one for each end of a memory object; in the 4-locker measure the cache locks waited 3 to 349 times in 1.19 to 1.74 million takes (the waits are the drains and the reports of other processors).
  54. 350. The sums stay exact. taken of a stack counts the reserved pages that the stack served and that reserved_pages still counts. A take from a stack moves 1 from its frames to its taken in one section of its cache lock, so the sum frames + taken of a stack changes only under the frame lock (a batch, a give back, a fold of taken, a drain). System_Report holds the frame lock and reads each stack under its cache lock: free pages = free_frames + the frames of the stacks, reserved pages = reserved_pages - the taken of the stacks, exact while the other processors take. So the check "budgets N pages: all the free pages" of the console and the checks of locks stay as they were. The invariant: free_frames + the frames of the stacks = the pages of all budgets + reserved_pages - the taken of the stacks.
  55. 351. No take fails while frames sit in a stack. A take that cannot fail (a reserved page, a list page, a page table) and a contiguous object, with no free frame or no run in the frame table, drain all the stacks under the frame lock (caches_drain) and take again. The global exhaustion was not produced in the machine test (a budget never holds most of the memory there); the test build drains all the stacks at each 64th refill of a stack, so the drain runs on the same path while the other processors take and put: 2 to 2 315 drains of each stack in a measure.
  56. 352. The check of the frames in the test build (TEST_BUILD_CHECKS_FRAME_SUMS, as TEST_BUILD_CHECKS_LOCK_ORDER): at each report and each drain, under the frame lock and each cache lock, the free bits of frame_bits must be free_frames, each frame of a stack must have its bit set, and the invariant must hold; else the machine stops with code 3 and the numbers. It held in each run of this step.
  57. 353. The zeros. A frame gets its zeros when a page takes it, after the locks, as before (region_frame): a program never sees the old content of a frame (P20), and a frame on a stack is not zero. Zeroing at the put would hold the frame lock longer (the put is in the section of the end of a memory object) and would zero frames that a full stack gives back. The other order was not measured.
  58. 354. The capacity, by measure (kerneltool measure, locks with only the desktop, the 4-locker case of pages on first use; the host had a load of 6 to 15 during these runs): with no cache, the frame lock was taken 1 344 697 to 1 424 677 times and waited 170 677 to 270 701 times; with 16 frames, taken 143 256 to 180 885 times, waited 22 980 to 31 744; with 64, taken 50 330 to 58 208, waited 5 769 to 9 742; with 256, taken 10 179 to 13 449, waited 392 to 1 183. Chosen: 256 (2 KiB of the processor record; at most 1 MiB of free frames held by each processor, which a drain gives back). A locker faults 256 pages and frees them: with 256 the frames of one round stay on the stack. Pages on first use in one second, 1 / 2 / 4 lockers: 1 411 413 to 1 448 106 / 2 486 613 to 2 515 626 / 4 447 573 to 4 712 106 with no cache; 1 195 520 to 1 495 040 / 2 121 386 to 2 924 373 / 3 944 960 to 5 663 573 with 256. These rates moved with the load of the host by more than the change. Measured again after the rebase on the system copy and fill (choices 310 to 315; three runs each, the same tree with the kernel of master and with this one, load 7 to 23): pages on first use in one second, 1 / 2 / 4 lockers: before 1 830 400 to 2 303 146 / 3 081 386 to 3 758 933 / 4 791 466 to 6 493 866; after 2 174 293 to 2 345 813 / 3 933 866 to 4 410 026 / 7 278 080 to 8 057 173. Four lockers did 2.6 to 2.8 times one locker before, 3.3 to 3.4 times after. The frame lock in the 4-locker case: before taken 1 448 671 to 1 963 381 times and waited 303 382 to 405 521 times; after taken 18 071 to 20 635 times and waited 770 to 968 times; the cache locks waited 18 to 33 times in 2.2 to 2.4 million takes.
  59. 355. Corrections after an independent read of the frame caches. (F1) region_create of an object of more than 512 pages took its first list page in one section of the frame lock and the others in a second section; the budget paid for all of them in the first. Between the two, the free frames were above the pages of the budgets and the reserved pages by the list pages not yet taken, and a report or a drain on another processor stopped the machine. The stress case of locks reaches it: two lockers make and close objects of 600 pages (three list pages), two lockers fault pages, and locks reads System_Report with no pause; the lockers are other programs, so no program lock orders their entries (a first version in one program never reached the window: the program lock serialized the reader and the maker). Before the correction the machine stopped: "kernel: the frames do not add up: free bits 114808, free frames 114808, in stacks 0, marked 1, budgets 107883, reserved 6923, taken 0" (2 list pages). Now each list page is taken in the first section, and the zero fill of the list pages is after it; in each run since, 109 412 to 162 890 objects were made while locks read 67 901 to 113 363 reports, 0 wrong. (W2) The difference free pages - reserved pages is exact; the two alone are not values of one instant: the comment of System_Report and ARCHITECTURE.md say it, and the leak check of the console and the checks of locks compare only the difference. (N1) The taken of a processor that frees as much as it takes never folds: the report subtracts it; the comment says it. (N2) The comment of frame_reserved_take says what makes processor_self safe there.
  60. 356. A test build and a measure build (W1). The two checks of the kernel are now TEST_BUILD_CHECKS_LOCK_ORDER and TEST_BUILD_CHECKS_FRAME_SUMS, and the kernel tool passes both in each build: on for check, test, screenshots, measure and run, off for measure without_checks and run without_checks. Before, the defaults of the source were on in every build. The second check scans frame_bits under the frame lock at each report and each drain, and drains all the frame caches at each 64th refill. In the measure build no drain happened in any run (0 for each processor), so each drain of the test build came from the check. Measured with only the desktop (three runs of each, load of the host 6 to 8):
locksTest buildMeasure build
Kernel entries in one second, 1 / 2 / 4 lockers (million)4.41-5.54 / 8.12-9.96 / 17.52-19.504.21-5.37 / 8.85-9.92 / 17.87-18.82
Pages on first use in one second, 1 / 2 / 4 lockers (million)2.22-2.32 / 4.11-4.34 / 7.42-7.751.92-2.34 / 3.58-4.21 / 6.77-8.40
Frame lock, 4 lockers: waits of takes1 003-1 923 of 20 442-25 002578-1 194 of 16 004-19 812
Stress: objects of 600 pages made in 500 ms109 412-119 053119 576-162 890
Stress: reports read in 500 ms67 901-71 10785 106-113 363
Drains of all the processors196-4350
schedulerTest buildMeasure build
Two threads that wake each other, wakes in one second: round robin / fair / EEVDF379-399 / 236 210-320 270 / 138 150-208 102399-400 / 205 737-278 669 / 196 525-234 986
Work of 8 spinners in one second (thousand million)8.8-11.611.3-14.7
One Strategy_Set, in the middle2.0-2.6 us1.7-2.5 us

The cost of the checks, as these runs show it: about 30 % of the reports and of the creations of large objects (a scan of 2 048 words of frame_bits at each report), and no difference in the pages on first use or the kernel entries that these runs can separate from the load of the host (the ranges overlap). The spinners did 25 to 30 % more work in the measure build in each run; their path enters the kernel only at the tick, so the part of the check in it is not shown, and the load of the host can be its cause. The stress cases ran in the two builds, with 0 wrong reports.

  1. 357. What a drain in the measure build needs, and why the machine test does not make it. The frame table becomes empty only when its frames are paid by budgets and in use, and the rest of the free frames are in the frame caches: the budgets reserve the frames, so a program can exhaust the frame table only with a budget that holds nearly all the free pages. In the machine test the root service keeps about 100 000 pages unpaid, and no program receives such a budget; a test of the real shortage needs a grant of most of the pages of the root service to one program, or a machine with very little memory where the desktop takes most of it. Not done in this step: the drain is tested on the same code path by the test build, while the other processors take and put.
  2. 358. The zeros at the put, by argument (point c of the read). A frame is zeroed when a page takes it (about 55 ns for 4 096 bytes with rep stosb, choice 312). Zeroing at the put costs the same for each frame that is used again, and more in total: a full stack gives frames back to the frame table, and those were zeroed for nothing; and the put is in the section of the frame lock of the end of a memory object, so 256 frames would hold the frame lock about 14 us longer for each object. It can make a first use about 55 ns faster (of about 700 ns), and it makes each end of an object slower under the one global lock. Not measured.
  3. 359. The name of a static variable of a procedure: one cause that each build showed (coordinator, 2026-11-11, after the full check of step 53 failed on "kernel.module is not the same bytes after a second build"; text corrected by 400). kerneltool repeat kernel 40 under a host load of 31 gave: 0 of 39 modules different, but 39 of 39 LLVM IR files and 39 of 39 objects different. Two IR files of the same source differed in ONE line: the name runtime::default_random_generator_proc-.state-N, where N changed at each build (7400, 7582, 8185, 8437 in four builds; the same with the address layout of the compiler fixed by setarch -R). The name goes into the object as a symbol and, with --data-sections, as a section name. The tool then rewrote the IR before llc (static_variable_names_make_stable): N became the order of the first use of the name in the file. After it, repeat kernel 6 gave 0 of 5 IR files and 0 of 5 objects different. What this proved: for 6 builds of one module, the rewrite made the objects equal. What it did not prove: that the objects of every module were equal from then on (repeat host 30 gave 1 of 29 IR files, objects and modules different, with the rewrite), or that the name was the cause of a differing module (it was not: the name is not in the linked file). N is Entity.id (global_entity_id, src/entity.cpp; the name is made in lb_build_static_variables, src/llvm_backend_stmt.cpp), and it changed because two threads created entities (400). Removed by 400: with one thread N is the same at each build (6 builds of console: state-8142 each time), and the rewrite hid the cause.
  4. 360. One pointer theme file for a whole theme, read in place (contract/pointer_pictures, docs/POINTERS.md, section 3; request of the owner, 2026-11-11: "notre propre format de stockage pour les curseurs [...] format texte [...] ou même binaire [...] je te laisse choisir"). Binary for the run time, text for the source (362): a header of 512 bytes with the name, the license and the attribution, a table of shapes, a table of pictures, the pixels. No parse at run time: one function validate checks each offset, count, text and pixel, and the tool and the compositor run the same function; 6 host tests damage each field (44 damages) and cut the file at each length. The pixels are 0xAARRGGBB with premultiplied alpha: the form of an Xcursor file, and the form of a canvas (0x00RRGGBB) plus the alpha in the high byte, so the blend takes each pixel with no conversion; straight alpha needs three more multiplications for each pixel. Two shapes can name the same pictures (Posy's grab and grabbing are one file). Not chosen: hyprcursor as the run-time format (a zip of PNG or SVG pictures with hyprlang manifests: a zip reader, an inflate, a PNG decoder, an SVG rasterizer and a parser in the compositor, for a gain in size that does not matter here); the owner named it during the step (docs/POINTERS.md, section 1).
  5. 361. Fifteen pointer shapes, by name (Pointer_Shape): arrow (0, the default of a zero record), text, pointing_hand, move, grab, grabbing, wait, busy_with_arrow, forbidden, help, crosshair, and the four resizes. Each has a CSS name, the source in the manifest of an Xcursor theme. The compositor uses arrow, move, pointing_hand and text from what it knows; the other shapes are for the request of a program. The CSS names with no use in the system are not taken; a new shape is a new number at the end.
  6. 362. The pointer manifest: one text grammar for two sources (docs/POINTERS.md, section 4, in 15 lines): a statement on each line (theme, license, attribution, sizes, shape, frame, and the lines of a grid after a |); a shape takes the first source file that exists in the Xcursor folder, through its links (8 at most, never out of the folder), or a grid of squares. Adjustments: a hot spot in pixels of the smallest size, which the tool scales for the other sizes, and the sizes of one shape. Each error gives the file, the line, the column, what was expected and what was found (23 host tests compare the sentences word for word). No scale of pixels: Posy's Cursor has the sizes 32 to 128, the tool keeps sizes that the file has and refuses another one with the list of the sizes of the file. The idea of the meta.hl of hyprcursor is the same (sizes, names, frames, hot spot); the hot spot here is in pixels, not a fraction of the size.
  7. 363. The theme of the system: 15 grids of squares in resources/pointers/system.manifest, under the license of the repository, in the style of the arrow of DESIGN.md (black at the edge, white inside), at the sizes 24 (squares of 2 pixels) and 48 (4 pixels); wait (an hourglass whose sand falls) and busy_with_arrow (the arrow with a turning bar) have 4 frames of 250 ms. It is always in the boot image: the system with no flake input has a whole theme.
  8. 364. The theme of the owner is a build input, and the default when it is there (owner, 2026-11-11, HANDOFF.md item 33). Its license is CC-BY-NC-4.0 (verified on 2026-11-11 in the meta of nixpkgs, the README and the LICENSE of simtrami/posy-improved-cursor-linux, and the page of Michiel de Boer: docs/POINTERS.md, section 6). nixpkgs marks that license unfree and not redistributable: flake.nix allows this one package by its name (allowUnfreePredicate), and gives its folder as POINTER_THEME_SOURCE. The tool keeps 32 and 64, and 32 only for the two shapes of 77 frames (1.26 MiB more at 64). The theme file carries the license and the attribution (who made it, the port, the license with its address, the sizes kept, no pixel changed), and the program pointers shows them.
  9. 365. Resources of the boot image (docs/ARCHITECTURE.md, U2b, the one description): a second table of Image_Entry after the modules (Image_Header.resource_count, which was a reserved zero), so that no reader of modules takes a resource for a module. The identity of a resource is the hash of its bytes (P11). No program reads the table by itself (P8): the root service finds a resource by its name, checks its hash (1.4 to 1.8 ms for 883 008 bytes in the loaded runs) and gives it as a read-only mapping (resource_mapping, programs/root/resources.odin); a program with the grant Image reads the table with system.image_resources. The choice of the theme is one function of the root service (pointer_theme_choice): a later setting of the person changes it there. The branch of the font uses the same mechanism for a glyph cache.
  10. 366. The pointer in the compositor (programs/compositor/pointer_picture.odin; 17 lines added to main.odin and 38 removed: an import, the field pointer_shape of a client, the call of pointer_theme_open, the wake of an animation in the deadline of the wait, pointer_update once in each turn of the loop, the case Pointer_Shape of a client command, pointer_draw and pointer_frame_end; removed: the arrow of squares and its constants, which moved to the new file as the arrow with no theme, and the two lines of damage of pointer_moved). It asks the nominal size 24: the smallest size that is at least 24 (32 for Posy, 24 for the system). The place of the pointer is unchanged (compositor.pointer() gives the same x and y); the picture is drawn with its hot spot there. pointer_update runs after each change of what is under the pointer, chooses the shape and the frame, and damages the old and the new rectangle of the picture only when the picture or its place changed. An animated shape wakes the compositor at the time of its next frame, and only while it shows. With no theme file, or a file that is not valid, the compositor draws the arrow of squares of before and writes why. compositor.pointer() has the field shape: the name of the shape under the pointer.
  11. 367. The blend, written from nothing (graphics.blend_premultiplied_alpha): red and blue in one 32-bit word, green alone, the division by 255 exact with t = product + 128; (t + t / 256) / 256; a pixel with alpha 0 is skipped, 255 copied; cut to the canvas. Host tests: each alpha, each premultiplied source and each destination of a channel (8.4 million cases), and a picture at each place around a canvas. Measured in the loaded runs: the arrow of Posy, 32x32 with 860 of 1024 pixels transparent, 824 to 1008 ns, against 151 to 153 ns for a copy of 32x32; the pointer in a frame, the mean of 256 frames, 1926 to 2040 ns.
  12. 368. The request of a program (display.pointer_shape_set, the display command Pointer_Shape): one shape for each surface, which the compositor shows only while the pointer is over that surface and the surface is the one in front at that place (P20: never over another program, the bar or a frame; the same rule as the serial of wl_pointer.enter in the cursor shape protocol of Wayland). A number out of the enum is ignored. The terminal asks for text; pointers asks for the shape of the tile under the pointer.
  13. 369. The program pointers (grant Image): each pointer theme of the resource table, its hash checked against its entry and its file validated (2.3 ms for Posy, 0.4 ms for the system, loaded), a tile for each shape with each size on a track, two amber lines through the hot spot, its frames, and the theme with its license, hash and attribution; two buttons change the theme. The window is built with the interface package; the pictures are blended into the canvas inside the tiles after the draw table, because the package has no record of a picture of pixels. The window of Posy drawn in 886 us.
  14. 370. The step Point of the machine test: it moves the pointer with no press, confirms its place, and reads the field shape of compositor.pointer() until it is the expected name (the tool reads a Text field of a record: record_text_field). With inside_window the place is from the corner of a window; a negative x is from its right side.
  15. 371. The log keeps 2047 records (LOG_PAGES 128 in contract/operation, 512 KiB of the kernel). The main machine wrote about 1024 records before the step that starts log and checks "0 lost": the first full check of this step failed there with "7 lost", from the 6 new lines of the pointer. The edge stays, at twice the place.
  16. 390. The default font is a vector font, on the grid of cells (owner, 2026-11-11, HANDOFF item 33; docs/FONTS.md). The font is monospace and its advance maps to exactly 8 pixels, the same scale on both axes: every layout in cells holds, no window changes its size, and CELL_WIDTH, CELL_HEIGHT and text_width do not change. The scales 2 and 3 are drawn from the outline at 16 x 32 and 24 x 48. A glyph is cut to its cell, so that a cell never draws into its neighbour (the terminal, the damage rectangles of the interface package). The baseline centers the ink of the characters 33 to 126 in the cell: row 12 at scale 1 for JetBrains Mono, the row of Spleen.
  17. 391. A grid fit of the vertical metrics, no hinting instructions. The baseline, the x-height and the cap height go to whole pixels (piecewise linear in the vertical, vector_font.place_y); the horizontal positions keep their thirds of a pixel. The subsets carry no instructions. The picture build/fonts/candidates.png has the default with and without it.
  18. 392. Subpixel bands with the filter of five taps (8, 77, 86, 77, 8 of 256): measured on a stem of one pixel, a fringe of 162 against 170 for three taps and 255 for none. Each pixel of the cache is one word: the three filtered coverages and the coverage of the whole pixel for grayscale. The order of the stripes is a setting: rgb, bgr (a swap at the blend, exact), none (grayscale).
  19. 393. The blend in linear light with a gamma of 2, in integers (graphics.blend_coverage_row): at most 8.9 levels from the curve of sRGB for the colors of the rules, against 54.9 levels for a blend with no gamma; a table of 4096 square roots that each program makes once, indexed by a shift.
  20. 394. A rasterizer in fixed point (24.8 subunits, i32 accumulations; the overflow bound is 8192 segments for each glyph; cuts that two pieces share): the sum of the coverage of a closed outline is its area, exact for edges along the axes and at 45 degrees, within 0.000029 of the area for others. It replaces the rasterizer in floating point of step 51, and with it the write outside a row for a vertical edge on the right side of the last column (host test the_last_column).
  21. 395. The glyph cache is built at build time and is a resource of the boot image, against a cache in each program (3.7 to 4.0 ms and 390 624 bytes for each program) and a font service (one more program, the font file at run time). It is a function of the font file (its BLAKE3 in the header) and of RENDERER_VERSION (P11). The root service checks its hash once (796 to 966 us) and maps it read-only into each program with a surface, each terminal and the compositor (P6, P20). It is a second resource of the mechanism of step 55 (choice 365), given by the same resource_mapping.
  22. 396. JetBrains Mono is the default font, as an ASCII subset among six candidates with glyf outlines (DejaVu Sans Mono, IBM Plex Mono renamed "IBM Mono" for its Reserved Font Name, Fira Mono, Commit Mono, Iosevka); Source Code Pro has CFF outlines only. kerneltool vector_fonts makes the subsets (14 to 21 KB each, from 156 KB to 50 MB) from the build inputs of the development shell; they are in resources/fonts with their licenses, so a build needs no build input. One line changes the default.
  23. 397. The settings page (contract/settings): one page that the root service creates, the program settings writes, and each program with a surface reads at a fixed address given in the value 13 of its context. A change wakes the compositor, which sends the display event Settings_Changed to each window; support/display gives the new font to support/graphics when the program reads the event, and the interface package draws the whole window. All zero is the default. The program settings serves set and get with error records.
  24. 398. Programs did not change for the font. The five private copies of large text read the bitmap of Spleen directly; each now calls graphics.text_scaled. The boot display swaps the stripes on a screen with red in the low byte.
  25. 399. The text from the glyph cache, measured: one glyph 309 ns against 35 ns for Spleen in the machine (333 to 362 ns against 33 to 44 ns on the host), after a loop for each stripe order, no bounds check after the one validation of the cache, and the squares of the pixel under kept; 921 ns before these. flood wrote 4 302 808 lines in one second against 3 925 316 on master in the same hour, the terminal drew 62 pictures in one second in both; the host load was 15 to 37 (above 8: not valid measures of speed by the rule).
  26. 403. A random generator in each program, a seed from its starter (docs/RANDOMNESS.md; the coordinator preferred this form to a kernel operation or a service call for each draw). Each program has a ChaCha20 generator with fast key erasure, a record in its own memory; a draw makes no kernel entry and no call. Its seed is 32 bytes in the context values 9 to 12 (op.CONTEXT_VALUE_SEED), which its starter writes from its own stream; the program erases them after it reads them. system.program_start refuses a seed of zeros. The root service has no starter: it gathers the seed of the system at its start. A child has no secret from its starter, which already chooses its code and its grants.
  27. 404. The seed of the system claims no more than its sources. BLAKE3 of 8 values of rdseed, 8 of rdrand and 2048 deltas of the cycle counter, each with its length. The strength is that of the strongest source that gave all its values: entropy (rdseed), processor (rdrand), or weak (the cycle counter, counted as 0 bits). A value with the carry clear, all ones, or equal to one before it is refused. The root service writes the report on the log, and gives its facts (never the seed) to the program randomness. With no rdrand and no rdseed, the system starts and says "weak".
  28. 405. The inline asm of rdseed and rdrand has #side_effects. Without it, LLVM took the instruction as a pure function: in a loop it kept one value, and the check of repeated values refused 255 of 256 (kerneltool test tcp showed "rdrand present, 1 good, 255 refused"). tcp.secret_make, which read rdrand in a loop with the same form, is gone: server and fetch take their secret from their generator.
  29. 406. The cryptography is code of Odin's core:crypto, taken into the repository (owner, 2026-11-11: "don't write cryptography from nothing, but maybe incorporate existing stuff and adjust it/provide wrappers functions/compat, so that they use OUR kernel primitives (tables, modules, shit like that)"; docs/CRYPTOGRAPHY.md). The files keep their arithmetic and their constant-time code. Only their imports change, to the copies and to a compatibility layer. kerneltool cryptography-origin proves it for each file of support/cryptography/origin.manifest: the origin in the compiler of the flake has the BLAKE3 hash of the manifest, and the origin with the listed changes is the file, byte for byte. The zlib license of Odin and the BSD-1-Clause of fiat-crypto are beside the code; each file says in its first line that it is altered. erase_secret_bytes in runtime/memory.asm erases a secret: a call into an object of nasm, which the compiler cannot remove with no link-time optimization.
  30. 400. One source gives one module: the Odin compiler runs on one thread, by a patch that the flake applies (tools/odin_compiler/thread_count_counts_the_main_thread.patch, flake.nix; property 11; the open fault of the gaps). The cause, in the source of the compiler of the flake (tag dev-2026-05): init_global_thread_pool (src/main.cpp) makes worker_count = thread_count workers beside the main thread, so -thread-count:1 gives two threads (counted: 2 threads in /proc/PID/task during a build). The worker steals the tasks of the main thread. In check_collect_entities_all (src/checker.cpp) each file is one task, and each task adds the entities of its file to the shared queue info.entity_queue. That queue gives the order of info.entities (check_add_entities_from_queues), and the checker walks the declarations in that order. That order changes the order of the instances of polymorphic procedures and of the types in the LLVM module, and the inlining of -o:speed changes with it. The entity counter (global_entity_id) changes too, and with it the name of choice 359. -no-threaded-checker changes only check_procedure_bodies. The parser, the collection of the entities, the exports and the dependency walks still use the pool. Measured with the stock compiler and the flags of the tool, the process pinned to one processor (taskset): console 3 distinct LLVM IR files in 60 builds (49, 10, 1), host 2 in 60 (56, 4). With no -no-threaded-checker and no rewrite: console 5 distinct modules in 25 builds (3, 19, 1, 1, 1) and 15 distinct IR files. The patch: -thread-count:N means N threads in all, the main thread and N-1 workers, as the backend of the compiler already counts (lb_generate_code). With one thread the main thread runs each task in thread_pool_wait, in one order. A thread that waits for a signal with no worker (wait_signal_until_available, the fields of an #soa type that a queued task completes) runs the tasks of its own queue: no other thread would run them. With workers that wait is unchanged. Measured with the patch, pinned to one processor: console and host 60 builds each, one IR file each, one thread in each compiler process. The patch changes two files of the compiler (28 lines added, 10 of them comments; 3 removed); the flake builds the compiler from source (about 5 minutes, one time). The cost: the Odin step of the 61 modules, the two compilers one after the other on each module: 39.2 s (stock, two threads) and 40.3 s (patched), then 32.9 s and 34.1 s, with the same processor time (36.5 s and 36.3 s, 32.4 s and 32.4 s); host load 17 to 28. Refused: (i) a flag or a variable of the stock compiler that gives one thread: none exists (-thread-count is the only one, and it gives two threads); (iii) -o:none IR from Odin, a sort of the IR by the tool, then opt -O3: it changes the code of each module and needs a canonical form of the IR that the tool must keep correct, and the cause stays in the compiler. For the upstream project (not sent: the owner decides): the patch file is the text of an issue and of a change; its first lines state the fault, the lines of the source and the measures. Not tested: the wait of an #soa type with no worker. The case needs a struct that is not complete when its #soa type is made; the small programs tried did not reach it, and two of them crash the stock compiler too.
  31. 401. The check and repeat compare each output of a build, keep each distinct result, and repeat all builds everything (tools/kerneltool/repeat.odin). The check of the second build compares, for each module, the module, its LLVM IR and its object, the pointer theme files, and the boot program and its object (before: the module only; choice 359 showed that 39 of 39 objects can differ while 0 of 39 modules differ). repeat <module> <count> counts the distinct results of each output and the builds of each ("3 distinct in 30 builds: 27, 2, 1"); before, it compared each build with build 1 only, so one rare first build made "29 of 29 builds differ". repeat all <count> builds every module and the boot program count times and prints a table: the module, the builds, the distinct modules, LLVM IR files and objects. Each distinct result stays as a file in build/different_builds/<module>/<file>.variant_N (a folder that no build resets: the build of a module resets its folder of objects). The tool prints the first differing line of an IR file, the part of a module file, or the first differing byte of another file.
  32. 402. The rewrite of the names of static variables (choice 359) and -no-threaded-checker (choice 189) are removed. With one thread, the name of a static variable is the same at each build, and the rewrite only hid the cause. -no-threaded-checker has no effect when the pool has one thread (check_procedure_bodies takes its sequential path when threads.count is 1). The tool kerneltool itself is compiled by odin run with the default count of threads (16 on this host). It is not a module, and property 11 does not cover it. Measured with the patched compiler and one output name: 6 builds of the tool with the default count of threads gave 6 distinct files, and 4 builds with -thread-count:1 gave 1. The flake keeps the default count for the tool: a faster start of each command, and no property needs the same bytes.
  33. 407. The compositor draws its screen as one draw table (screen_build): for each window its frame records and its surface as a Picture record, then the bar and the menus as cards of the package. The table is built once for each frame and executed in each damage area; the damage of the compositor stays its own, because a window that moves moves its surface. The frame of 800 x 600 costs 8.9 us as 9 records against 8.5 us by hand (one measure run under load: an indication).
  34. 408. The boot display uses the card of the package, in the UEFI loader too: the core files of the package make no kernel call. A fact longer than its card is cut after its last whole word.
  35. 409. paint, plasma, temple show their pixels as Picture records; a program that is one picture calls interface.show_picture. paint raises the generation of its paper at each change, including the changes of its exports.
  36. 410. The text grid record (Text_Grid, draw record version 3): the cells stay in the memory of the program, each table before keeps a copy, the difference damages the span of changed cells in each row. The console needs nothing more: it writes records to its terminal.
  37. 411. With two pictures a frame presents its difference with the picture on the screen too (damage_of_two_pictures). A frame equal to its own picture can differ from the screen; the terminal showed rows of two frames before this rule. A host simulation of two pictures and a screen holds it.
  38. 412. Window places on the grid of 8: new windows start at 64,56 and step by 40,32; a moved window snaps its corner to the nearest place of the grid. The scenarios move windows with steps Move, and the tool reads the places from compositor.windows() once it has the grant person.
  39. 413. kerneltool measure windows: a measure build (-define:MEASURE_RUN=true) starts each converted window alone and prints the time of its first picture and the means of 3 and 10, with the load of the host; above a load of 8 the table says that it is not valid. The lines of the measure are never in a normal build: the log ring of the main scenario has little room.
  40. 414. Parallel module builds: one process of the tool for each module, in levels of imports (tools/kerneltool/parallel_build.odin; owner, 2026-11-11, HANDOFF item 43). kerneltool build runs the command module <name> with_checks|without_checks measure_run|usual_run of the tool itself for each module of the table, at most one process for each processor at once, or build <processes>. Each process runs one compiler on one thread (choice 400), so one source still gives one module. A module needs the files of its imports only at its conversion, so the modules are built in levels: level 0 has no import, and a module is one level after the highest level of its imports (today 2 levels: test_caller, agenda, lonely and imports are in level 1). The tool keeps the standard output and the standard error of each process, and prints them whole, in the order of the table, after the level. After a failure no new process starts; the tool names each module that failed and gives the count of the modules that did not start. The boot images are assembled after the modules, in the order of the table. Each module writes only in its own folder, in <name>.module, and in <name>.ll in the folder of the repository (the compiler writes its IR where it runs); the tool refuses a table with two equal names. Separate processes, not threads in the tool: the build of a module uses global state of the tool (the temporary memory, test_build_checks), and a process gives its whole output and its own exit code. The check "the same bytes after a second build" and repeat all use the same parallel build (repeat all <count> <processes>); repeat <module> builds one module in its own process, as before. Measured (kerneltool build, the whole command with the boot programs, the two forms in alternation, host load 20 to 46, so above the limit of 8 for a valid measure): build 1 171 s and 99 s, build with 16 processes 82 s and 58 s. A parallel build of all modules gave the same 63 modules and boot program as the sequential build (compared byte for byte). A broken source of life gave its whole error, "the build of life failed", "54 of 63 modules did not start, because a module failed", and exit 1. Not run by this agent (the rule of 2026-11-11 for subagents: the coordinator builds and tests): repeat all 100 with the parallel build, the full check, the flake.
  41. 415. The compiler is the release dev-2026-11a of Odin, pinned by the flake (flake.nix; owner, 2026-11-11, HANDOFF item 56). The flake fetches the release by its tag and its hash and builds it with LLVM 18, the compiler and its base and core libraries only. What the six releases since May changed for this system: -out of an LLVM IR build is a folder (the tool gives an empty folder and takes its one file); the inline assembly by a string is gone, so rdrand and rdseed are asm templates with the carry flag as a result (support/random/entropy.odin); the analysis of atomic accesses found reads by volatile_load of words that other code writes atomically, in the ring, the wait and three programs: they are atomic loads now; odin run gives a program only the name of its file, so the parallel build starts the file of the process; a stricter conversion in fetch; core:compress in the tool. The patch of the thread count is one line: the wait that its second part corrected has no caller left. The files of ChaCha20 are taken again from the new release. kerneltool check: exit 0, 835 checks, 64 modules the same bytes after a second build.
  42. 416. The compiler has no patch and uses all its threads (flake.nix, tools/kerneltool/modules.odin; owner, 2026-11-11; docs/REPRODUCIBLE_BUILD.md, section 7). With the release dev-2026-11a, the output of the compiler does not depend on the count of its threads: kerneltool repeat all 20 with the stock compiler and no -thread-count gave 1 distinct module, LLVM IR and object for each of 64 modules and the boot program. The patch of choice 400 and the option -thread-count:1 are removed. The wider patch of the compiler for a deterministic order with several threads, the last item of the list of the owner, is not needed: the compiler has that property now.
  43. 417. Two parameter types, one direction for each parameter (owner, 2026-11-11: "ok bah on le fait alors ?"; MODULE_CONTRACT.md, "Tables and bytes as parameters"). A Table parameter (the type 5, a result before) and Bytes (6), each with a direction: Read (0), Change (1), Append (2). Read is 0: the least power is the zero. Append covers fill (from a count of 0) and append (the draw table of the interface, which each element call extends): the callee writes after the count, reads none before it, and gives the count of the full answer, larger than the capacity when it did not fit, as a Text result does. The Table result stays as it is: it is the case "Append from 0 in the last place, whose count is the integer result". Refused: tables of tables, a table in a record, an address in a record, a Handle in a record of a table, a direction on another type, a capacity rule in the description. A record can name bytes of another parameter by an offset: data, not an address; the export checks it (the error kind Out_Of_Range of master, whose found is now the number of the record that names the bytes).
  44. 418. The table place: a Table or Bytes parameter takes one register, the address of {address, count, capacity} (24 bytes, three u64) in memory of the caller. Three u64 is the same layout in C, Odin, Rust and Zig; an Odin slice {data, len} has no capacity. No record size travels with it: an import names the exact module by hash, and the check of a replacement compares the record and the direction of each parameter (Difference.Parameter_Direction). The rule of the table directory for a shorter record is for the tables of a file, not for parameters. In Odin the type is the direction: system.Table_To_Read(Reading), Table_To_Change, Table_To_Append, Bytes_To_Read, Bytes_To_Change, Bytes_To_Append.
  45. 419. The format grows by its own rule: Function.parameter_directions (6 bytes, and 2 reserved) at the end of the Functions record, 96 to 104 bytes. A reader of a file with 96-byte records reads Read for each parameter. No version and no feature bit: each module is built with the system (owner: no backward compatibility), and a module of before still loads. Integer fields of 1, 2, 4 and 8 bytes: Field.detail is the size of an Integer (0 for 8, the fields of before), Field.unsigned takes a reserved byte; an Integer is at a multiple of its size. module.field_integer reads one; the console, the tool and the manual read fields with it.
  46. 420. The checks of a description are where a reader uses it, as before: the loader does not read descriptions. module.record_type checks the size and the alignment of each Integer field; isolated.record_size refuses a direction out of the enumeration or on another type, a Table whose record is not valid or has a Handle, Bytes with a detail; information.check gives the problem Direction (12) with its byte.
  47. 421. The isolated call carries a field of 32 bytes {start, count, capacity, memory}. With memory 0 the caller copies the records of a Read or Change table into the call region, and reserves the free records of an Append table (the records before the count are not in the call: record count of the place of the receiver is the first reserved byte). The receiver copies the argument record once, checks each field (a start at a multiple of 8, the records inside the region with no overflow, count <= capacity for Append) and gives the export a table place in its own memory. After the export it writes each Append count back into the field; the caller copies back the records 0 ..< count of a Change table and count ..< min(new count, capacity) of an Append table, never at or after the capacity, and refuses a count smaller than the count on entry. 10 host tests in contract/isolated/tables_test.odin, among them a guard pattern after the capacity with a callee that writes the whole call region, 100 000 random fields, and each hostile field.
  48. 422. A table in a memory object (form iii): the field names a lent memory object; the rights of the lent copy are the direction (Read; Read and Write), and the receiver proves them as each lent handle (a Change table lent with Read only gives Bad_Handle: a case of tables). The receiver maps only the pages of the records, at isolated.TABLE_ADDRESS + n * 16 GiB, the same addresses at each call: with an address that the kernel chooses, each call took a new range and new page tables, and the call host refused the 61st call (found by the measure). system.call_isolated lends the memory object only when the records do not fit in the call region: the call region costs less at each size that fits.
  49. 423. One caller side for the three ways (support/system/calls.odin): call_direct (the registers of the call form over table places on the memory of the arguments, no copy), call_isolated (lend, encode, the wait, the copy back, the revocation; it gives the handles of the caller back in the arguments after the call), call_host_start (the start of a call host from the boot image, as the console does it) and service_connection. The console keeps its own call host code; moving it to call_host_start is a next step.
  50. 424. sample has an entry: the same module is a library (load), a call host (isolate) and a service that the root service starts on demand (connect("sample"), the grant Serve). Its exports: readings (a Table result), sum_values (Read), scale_values (Change), append_readings (Append), sum_words (a table whose records name bytes of a Bytes(Read) parameter, with an error record Out_Of_Range for a word past the end), upper_case (Bytes(Change)). The exports that are wrong on purpose (append_past_capacity, an Append export that writes past its capacity, and keep, kept, hold, look of the tests of a lent handle) are in the module hostile_exports, with no entry: no service holds them (finding B1 of the hostile read: the service sample held append_past_capacity, and remote reached it, so a host of the network made the service write past its call region). remote does not reach sample.
  51. 425. The console: a Table value is an argument where the description takes a Table of an equal record (module.records_equal: columns gives another record, which is refused with a sentence), with the direction Read or Change; a Text is an argument of Bytes(Read). A Change table changes the memory of the value: a variable holds the changed rows. An export with an Append table or Bytes(Change) or Bytes(Append) is not a command of the console: the language has no second result. One text of a signature in the console, the manual and the errors: sum_words(words: Table(Word, Change), text: Bytes(Read)) -> Integer.
  52. 426. kerneltool call: a table argument is a literal of rows, each row the values of its fields in order, [(1, "a"), (2, "b")]; Bytes(Read) take a text. A Change or Append table is refused: the answer of remote carries one result place. The test calls monitor.counts_named (a Table(Count_Name, Read): many counts in one call); remote does not reach sample.
  53. 427. The interface package as a module for one program (modules/interface_module, programs/interface_through_module; INTERFACE.md, section 7, "The test of the claim"). Eight exports call the package with no line of the package changed. An export makes a frame of the package over the two tables of the caller for one call; the interface state and the input point to two empty records of the module, which these elements never write. The first form zeroed 1.5 KiB at each call: 26 ns for each element call, 10 ns after the change.
  54. 428. Measured (MODULE_CONTRACT.md, "Measures"; the load of the host 32 to 48: not valid by the limit of 8, the numbers of that run): sum_values, one call, 64 / 1024 / 65536 records of 40 bytes: direct 12 / 250 / 26 284 ns; call host, call region 6 671 / 8 787 ns / does not fit; call host, memory object 12 500 / 13 146 / 76 422 ns; service, call region 3 327 / 8 217 ns / does not fit. One frame of 31 draw records in 25 element calls: 523 ns through interface_module, 283 ns with the package compiled in. The code: interface_module 14 980 bytes; the functions of the package that the compiler kept in clock 8 865 bytes, in interface_elements 24 948 bytes. The full check after the rebase on step 55 (the load 21 to 23; the package had changed under the module, with no change to the module): direct 15 / 348 / 26 051 ns; call host, call region 7 712 / 10 852 ns; call host, memory object 17 868 / 19 338 / 110 615 ns; service, call region 3 102 / 9 010 ns; one frame 741 ns through the module, 370 ns compiled in; interface_module 24 576 bytes of code pages.
  55. 429. The corrections of the hostile read (2026-11-11, review_tables.md). F1: an Append place with a count near 2^64 passed decode in the call region (start - count wrapped), and system.bytes_append added the count and an index with a wrap: 11 bytes past the call region. decode now refuses a count or a capacity over TABLE_SPAN / size in each form; bytes_append compares the index with the free bytes, and its count, as the count of table_append, stops at the largest number. Tests: decode_refuses_counts_near_the_end_of_the_numbers (contract/isolated), bytes_append_near_the_end_of_the_numbers (support/system, now in the host tests of kerneltool check); both failed before. F2: interface_module.execute_in_rectangles checked stride * height * 4 with a product that wraps (a stride of 2^62 + 1): it now compares the stride with the pixels of one line that the bytes hold. W4: a text record that names bytes past its text table stopped the module in damage_by_difference, and a capacity of 2^63 was a negative int: the module refuses a table over 2^40 records or bytes with -1 (the check of each text range moved into support/interface in 429). Tests: canvas_shape_that_wraps_is_refused, hostile_records_are_refused (modules/interface_module, now in the host tests of kerneltool check); both failed before. W2: sample.sum_words read the start of a word two times from the shared call region: another thread of the caller made a slice that stopped the service; it copies the record once with a volatile load (the rule "an export copies a record before it checks it" in MODULE_CONTRACT.md). Test: a_record_that_changes_during_the_call (modules/sample, a thread changes the record during 2 000 000 calls; it stopped on an Illegal_Instruction before). W5: interface_through_module used the count of an Append table as it came: it draws min(count, capacity) records, and the scenario builds one frame into a draw table of 16 places ("the frame did not fit, 17 records asked, 16 drawn"). Corrected again in 429: the count is the count of the full frame. W1: a service refuses a table in a memory object (export_serve takes memory_objects_allowed, true only in the call host): a caller that revoked the lent copy during the export stopped the service for each caller; case of tables: "a memory object to the service". W3: decode refuses two ranges of the call region that overlap when one is written; host test decode_refuses_ranges_that_overlap. W6: system.call_direct checks the description with record_size, refuses a result that is not an Integer (Not_Done; it gave the address of the error record where readings reads its buffer) and a table whose record is not valid; case of tables: "call_direct of a Table result". W7: tables_return checks each answer, then copies (a problem changes no count), with Bad_Description; call_isolated gives the parameter in the error record; host test tables_return_changes_nothing_on_a_problem. W8: each refusal of the receiver is an error record in the completion; host test decode_refusal_is_an_error_record, cases of tables with their sentences. N1: a record size is a multiple of its alignment (host test). N2: a table in a memory object reaches at most TABLE_SPAN less one page (host test). N3: a table names a memory object (case "a budget as the memory of a table": Handle_Kind). N4: a value that its Integer field does not hold is refused with a sentence in the console and in kerneltool call (module.field_integer_fits, host test; 300 in a u8 became 44). N5, N7: in MODULE_CONTRACT.md. N6: remote zeroes its call region after the arguments before each call: a request reads no bytes of a call before it (no test: the tool has no request that names stale bytes). N8: the wording, and a redundant check of information.check. Names of the review: memory_handle, writable, count_offsets, lent_handle_add, is_table_or_bytes, lend_one_handle, mapping_address, records_in_region, reachable_records, count_given_by_callee, kept_records, draw_table_to_read_of, table_description_of. The fixed address of a lent table stays a work-around of the kernel fault "a mapping with no chosen address never reuses a range", given to work/scheduler.
  56. 430. The corrections of the second hostile read (2026-11-11, review_tables_second.md). F-new: interface_module used a count over the capacity on entry as a mark that the contract refused; it worked in a direct call only, and an isolated call gave No_Room with a wrong reason. Decision of the coordinator: the count of an Append table is the count of the full answer across calls, and on entry it can be over the capacity (MODULE_CONTRACT.md, "Did not fit, across calls"). isolated.table_encode and table_decode take it (the records in the region are capacity - min(count, capacity); record min(count, capacity) is the first reserved byte), tables_return copies min(count, capacity) ..< min(new count, capacity), system.call_direct takes it. interface_module builds each element into an element frame of its own on the stack (64 records, 4096 bytes of text; a larger element gives -1) and appends it to the tables of the caller with system.table_append and system.bytes_append: the counts of the full answer, also of the text table. Tests: append_across_calls_is_the_same_in_each_way (contract/isolated: three calls on a table of 4 places, the same counts 3, 6, 9 and the same records in a direct call and through encode, decode, counts_store and tables_return), append_after_a_call_that_did_not_fit (support/system), counts_of_the_full_answer_across_calls (modules/interface_module), the scenario line "records asked, 16 drawn, the count of the full frame". W-new: the check of the text range of each record moved from the module (a check in shared memory, then a second read in the package) into support/interface where it reads: records_equal gives "not equal" for a text range past its table, and damage_by_difference takes one volatile copy of each record; test text_range_past_the_table_is_damaged. Notes: (1) export_serve proves the lent handles and defers their close before the refusal of a memory object to a service; (2) remote zeroes its call region with one fill; (3) Overlap with the unit Region is an offset in the call region, and Absent names the row of a table argument in found, with their sentences; (4) a handle that fails the proof gives Handle_Absent with the unit Lent_Handles ("names no handle that the caller lent for this call"), or Handle_Rights with the rights asked and held.
  57. 431. The real shortage of frames, in a machine of the test (part A of the step after the frame caches). The frame table is empty only when the unspent pages of all the budgets and the reserved pages are fewer than the frames in the stacks (at most 256 for each processor): on the machine of the desktop the programs keep thousands of unspent pages, so it never happens there. A new machine of the test has a boot image with no input service (build/boot_shortage.image); the root service sees it and starts only the program shortage, with all its pages but 24, and the machine (Inspect, Stop). Four threads make objects and write each page (takes that cannot fail) until the budget pays no more; the last pages of the budget become one object that is not used. Measured (one run of each build, load of the host 26 to 43): 116 692 pages written in 2 163 ms with the checks of the test build, 23 drains; 116 694 in 2 857 ms in the measure build, 3 drains: each one a real drain, the measure build has no other. Then a new object of 1 page fails with No_Budget and the error record of kind Budget, while the report says that the budgets can pay 17 pages (those that the root service kept) and 192 pages are reserved: a take that can fail fails on the budget, before any frame; the frames that remain are the frames of other budgets. Everything is freed in 5 to 7 ms, and the free pages that budgets can pay are those of the start less 231 pages of page tables, which Memory_Unmap does not give back (the gap of choice 224) and which return when the program stops. The machine stops with the code 0. kerneltool test shortage, kerneltool measure without_checks shortage; the main test runs it after the machine with no compositor. No picture: the machine has no desktop.
  58. 432. A program drives its own virtual memory (owner, 2026-11-10: "a program drives its own virtual memory", "use paging and virtual memory as a design tool"; ARCHITECTURE.md, "The virtual memory of a program"). An uncommitted object (Memory_Create with the new flag Uncommitted) pays only its frame list; its mapping holds a range of addresses with no memory behind it, in one mapping of the 128 of a program. Memory_Commit (operation 28) pays pages of a table of ranges, and with Decommit takes them back at once; Memory_Protect (operation 29) changes the protection of pages of a table of address ranges, with one shoot-down. Why two operations and not kinds of existing ones: no existing operation changes what a budget pays for some pages of an object that lives (Memory_Create makes one, Memory_Map and Memory_Unmap change addresses), and none changes the protection of some pages of a mapping that lives. Why one operation for commit and decommit: the two change the same three counts (the budget, the reserved pages, the frames) over the same table, in opposite directions. The kernel had 27 counted operations; it has 29.
  59. 433. A table of ranges in one command. Memory_Commit and Memory_Protect take at most 256 ranges in memory of the caller (P13, P14). The kernel ring already batches 32 commands for one entry, but each command takes the program lock again and checks its handle again, and a protection change would do one shoot-down for each; a table takes the lock once, checks once, and does one shoot-down. Measured: kernel entries for 1000 pages committed and used, with 1, 16 and 256 ranges in each call: 1000, 62 and 3, in each run (the first use of a page is a fault, not an entry).
  60. 434. The state of a page is in its slot of the frame list. A frame is aligned on 4096 bytes, so the low 12 bits of a slot hold: committed (for an uncommitted object), deny write, deny execute, no access. So the protection of a part of a mapping needs no new mapping (no split of the 128 mappings), and a page that gets its frame later gets the protection of its slot. Each reader masks the frame (region_frame). The cost: the protection of a part, and a decommit, need an object that only this program names, with one mapping (Bad_State with its error record otherwise): the slots belong to the object, and its entries must be in one page table. A contiguous object (device memory, large pages) has no frame list: its protection is of the whole mapping.
  61. 435. Decommit in two steps. First the entries of the pages are cleared and each processor that uses the page table forgets them (one processor_sync); then the frames go to the frame cache of the processor, the budget gets each page back. The program lock keeps the faults of the program out between the two. The content of a page committed again is zero: it gets a new frame (P20, ZII); the program checks it.
  62. 436. Large pages for an object made Large_Pages: contiguous, in one run aligned on 2 MiB (frames_take_aligned), a whole number of 512 pages (else Bad_Operation with its error record). A mapping whose address, first page and size are whole large pages uses one entry of the second level for each 2 MiB; another mapping of the same object uses pages of 4 KiB. The walk of the page table gives the entry of the second level for an address in a large page; the end of a mapping clears it; the free of the page tables of a program does not free a large page as a table; a table of the last level that a range left returns to the budget after a shoot-down when a large page takes its place. A protection change of a part of a large mapping is refused (the whole mapping changes). The kernel decides the size of a page from the object and the mapping: the program asks only for an object whose frames allow it.
  63. 437. The fault ring is designed, not built (ARCHITECTURE.md, "The fault ring"). It needs a wait of a thread that no word ends, a record that names the faulting thread and its serial, and a rule for a thread that stops while it waits for its answer: three new states of a thread, which deserve their own step and their own review. A guard page that stops a program is built: Memory_Map and Memory_Protect take No_Access.
  64. 438. The program address_space (window through support/interface): an arena of 1 GiB that commits 3 x 4096 pages with 1, 16 and 256 ranges for each entry; a sparse table of 1 GiB with 1024 pages committed at random; the decommit of each, with the budget back at once; the protection of 64 pages of a mapping of 256, 200 calls, with 0, 1 and 3 other threads of the program running; a child program that writes down its stack into a guard page; 64 MiB of large pages read at random through a mapping of large pages and through one of 4 KiB; four refusals with their error records (a range outside the object, a commit over the budget, write and execute, large pages of 100 pages). The checks do not depend on the load of the host: the committed pages and the pages with a frame of the report are 1024, the decommit gives back 1024 pages, a page committed again is zero, the report says Protected, Uncommitted and Large, the child stops on vector 14 in its guard page, each refusal has its status and an error record that is not Refused. The test build checks at the release of an uncommitted object that its committed pages are its slots with the bit, and the sums of the frames and the present pages of each mapping as before.
  65. 439. Measured (kerneltool measure address_space, only the desktop; the host had a load of 19 to 60 in every run, so the speeds are not valid by the rule of 8; three runs with the checks of the test build, two without). The arena: 1 GiB in one mapping paid 513 pages of frame list; the map of 1 GiB (its page tables, 2 MiB) took 3.7 to 9.8 ms (30.6 ms in one run). Pages committed and used in one second, with 1 / 16 / 256 ranges for each entry: 3 924 to 864 521 / 88 860 to 953 255 / 93 239 to 896 679 with the checks, 17 020 to 141 844 / 142 797 to 180 115 / 92 517 to 117 381 without: the load decides these numbers. The decommit of the sparse table: 1 268 to 1 822 us for 1 GiB of slots. One protection change of 64 pages with its shoot-down, in the middle of 200: 2 776 to 3 411 ns with 1 processor running the program, 2 822 to 9 639 ns with 2, 16 865 to 23 447 ns with 4: each other processor that runs the program costs its interrupt and its reload of the page table. One random read over 64 MiB: 13.4 to 36.1 ns through large pages, 12.4 to 52.3 ns through pages of 4 KiB of the same object: under KVM the guest walks are short when the host backs the guest with large pages, and these runs show no difference that the load does not hide.
  66. 440. Corrections after the independent read of the virtual memory (each with a case of address_space that failed on the kernel before its correction: the six were run on the kernel before and gave WRONG, then right). F1: a decommit wakes each thread of the program that waits on a word of a frame that returns, before the frame goes to the frame cache. F2: word_address on a large page keeps the bits 12 to 20 of the address. F3: an object whose pages have marks of protection is not mapped again and not given (marked_pages, kind Object_Marked); a part that gets the protection of its mapping has no mark. W1: a fault in a large mapping that its entry permits now (an old translation before a shoot-down) runs the instruction again; corrected by reading only, with no test (see the gaps). W2: an uncommitted object pays at least one page (a budget with no page cannot hold a slot of the table of memory objects). W3: frames_take_aligned moves its candidate above a used frame; one call changes at most 65 536 pages (Too_Long with its record; the longest decommit call is logged). W4: a part cannot get more than its mapping has now (Bad_Operation, kind Bad_Value); a protection above what the mapping was made with is Bad_Operation too (it was No_Right with a Refused record). Notes: a page that is not committed stops its program with the code STOP_CODE_NOT_COMMITTED (0x300) and the sentence "page fault on a page that is not committed"; a range of 0 pages is Too_Small; a protection of a part of device memory, of large pages or of a thread area is kind Whole_Only; an object held with no mapping can be decommitted; the attribute @(private = "file") of read_memory_pages is back on its procedure.
  67. 441. Who pays a commit (owner, 2026-11-11, "budget delegation", and the amendment of the coordinator): Memory_Commit takes a budget handle that must be the budget of the object, with the new right Commit (narrow: it makes nothing) or Spend; the right Write on the object no longer spends the budget of its maker. The ten cases of ARCHITECTURE.md ("Who pays a commit") run in address_space with a second program, each with its error record; new kinds Not_Payer, Object_Shared, Object_Marked, Whole_Only. Verified: a revocation of a budget handle does not end an object that was made with it.
  68. 442. The addresses that the kernel chooses are used again (the fault found by the agent of the tables): the first gap between the mappings, in place of a counter that only grew. address_space maps and unmaps one object 100 000 times: its budget holds the same pages after as after the first time. The run before the correction was not made in this step: the other agent's call host refused its 61st call.
  69. 443. Corrections after the second read of the virtual memory. B1 (blocking): an Uncommitted object of 1 to 4 pages paid one page that reserved_pages did not count, and the next report stopped the test machine; that page is a reserved page now (taken back at the release). F1': the wake of a decommit, and of a revocation (origin.odin, older than this step), also reaches a thread that registered its words and does not wait yet: it gets woken, and its wait_end does not wait (thread_words_wake); corrected by reading only: a test needs a decommit between the registration and the wait of another thread. F3': unmap ends the marks of protection of its pages, so the object can be mapped and given again. F4: the page limit of Memory_Protect counts one entry for each large page; a whole mapping of 4 KiB pages above 256 MiB is refused (a program that names its object alone changes it in parts); corrected by reading only: the test machine has no 256 MiB of large pages to give. W5: the wake of a decommit sorts the frames of the words and searches each page. W6: the error record of a commit reads at most 65 536 pages of the table. W7: an object of large pages mapped at an address that the kernel chooses gets an address on 2 MiB, and large pages. Notes: one table that names one mapping whole and in part is refused; chosen_address stops below MODULE_BASE; a Memory_Map into a stopped program names the state before the marks. Each case that a program can make is in address_space (B1, F3', W7, the table), and failed on the kernel before (B1 stopped the machine: "the frames do not add up"). The picture of address_space: the shoot-down shows the least and the most of five batches, since the program cannot know the load of the host.
  70. 444. Corrections after the third read of the virtual memory. The comments and the names of the cases of address_space say what each case does, with no review label ("a small object with no frame keeps the sums of the frames"), as the rule of the owner on comments asks; the cases are its "edge cases". A revocation that ends a mapping wakes the threads with a word in a frame of the object with one sorted search over the frames of the object (waiters_on_frames_wake, shared with the decommit), not one walk of the object for each word: with an Uncommitted object of 1 GiB, about 5 x 10^8 slot loads became one pass, which ends at the last frame of the object. unmap walks one table of the last level at a time, skips a range with no table, and stops after the last present entry of the mapping (the test build walks the whole mapping, to compare the count with the walk). region_release stops after the last frame of the object, and the test build checks the committed pages before it takes the frame lock. A whole protection change of a mapping of 4 KiB pages counts its present pages against the limit of one call, and walks its entries in the same way: a mapping of 1 GiB of an object that several programs name can change its protection whole. The error record of a protection that the limit refuses counts as the kernel counts (a large page is one entry). The comment of thread_words_wake says that the words can be dropped at the start of the next entry with no program lock, and why that is harmless. Not_Multiple (36) is after Whole_Only (35). Two cases of address_space were added: a revocation of a mapping of 1 GiB with a thread of the child that waits on a word of it (the waiter wakes at once; the time of the revocation is logged), and an Uncommitted object of 1 GiB with 4 frames that ends (each page returns; the time is logged). None of this was built or run by the author: a new rule of the owner gives the build and the tests to the coordinator.
  71. 445. A version is the state of one object at one commit, and a snapshot of a store is the versions of its objects at one generation: one structure serves both (contract/store, format STORAGE2). Each object names a version index (one block, up to 254 leaves); a leaf holds 85 version entries of 48 bytes: 21590 versions for one object. A table has 39 objects (it had 48): the entry of an object grows to 104 bytes with its time, its date and its index. The disk format changed: a disk of STORAGE1 is not read (no backward compatibility, the owner's rule).
  72. 446. Each commit that changes an object makes a version, with no call of the program. What it costs, exactly: the bytes of the version are on the disk already (copy on write never wrote over them); the commit keeps them in place of making them free. It writes 2 more blocks (the leaf and the index) and reads none after the first commit of the object (a cache of 16 blocks of the service, found by number and check). A block of data whose bytes did not change is named again, not written again (one read to compare its bytes). So a key in notes (one block of data) writes 6 blocks (data, list, leaf, index, table, root) where it wrote 4. A version holds its list and its blocks that no other state names (own_blocks): 2 blocks for a key of the note, 4 for a step of the count (its 3 blocks change at each step). The time of 1000 commits of the count: 3224 ms recorded at step 31; 1736 ms and 3327 ms in two runs of this step on a host where other machines ran: the cost in time is below the noise of the host.
  73. 447. The blocks of a store are its distinct blocks, and the limit of the grant counts the versions: a shared block counts one time. The service keeps a reference count of each block in use (16 bits) and the store that names it; the open makes them again from the disk. A block named from two stores, or a count of a store that is not its distinct blocks, is a fault of the engine on the log (new counts of the open).
  74. 448. Thinning at the limit is in the commit of the change: the oldest versions of the store (by generation, in any object) go until the change fits. A loss of power never keeps a thinning without its change (the second loss of power of 38b falls on such a commit). A change that the current objects alone cannot hold is refused before any thinning: a refused change destroys no history. Its error record: Over_Limit, "with no earlier version kept".
  75. 449. The retention rule (support/storage_retention, host tests): each version is kept 10 minutes; then the newest of each hour until one day; then the newest of each day. An hour and a day are counted from the format, so a kept version stays kept. The service applies it to an object at a commit of that object, at most one time a minute. The times are the dates of the versions when the system has a date (the time since the format does not count the time when no machine ran); a version with no date keeps its age in the time since the format (the root keeps it; the service adds its own time).
  76. 450. Five exports, each with its description and error records: versions (a Table), version, restore (a commit whose object names the list of the version: no block of data is written; the state before becomes a version), compare (a Table of byte ranges; two equal pointers are equal bytes and are not read), clone (the new object names the same list). Their first argument names the store: empty for the store of the caller; another store needs a caller that acts for the person (No_Grant Person, -10), as the console and the remote service do. A remove ends an object and its versions.
  77. 451. notes has undo to any version with no code to keep them: Up and Down step through the versions of the note (it reads versions and version), Enter restores the version shown, Escape goes back. The program commits each key, as before: the measure of one commit of the note is about 4 ms in the machine test.
  78. 452. The crash search checks the versions: each image gives each object and each of its versions, byte for byte, as the durable image or as the image with every write. A second loss of power falls during a thinning at the limit. The tool keeps the check of each block of the durable image that no pending write touched: the 306 images of a disk with 1119 versions take 6 s, not 15 minutes.
  79. 453. The real-time clock is read by a program, date, which receives the two ports and the date page with Write: a driver is a program, and the root service keeps no clock code. It reads the clock at the edge of a second, and its service gives now().
  80. 454. The date lives in a page that each program maps read-only (contract/date, the context value 15), on the model of the settings page: the date of the time 0 of the system. A program adds the time of the time page: no kernel entry, no call. A program that starts before date wrote the page reads no date, and the bar shows the time since boot until then.
  81. 455. UTC, no leap second, a correction of drift each 10 s: the time zone is a field of the page that no setting writes yet; a day has 86400 seconds. The first full check found the date 22 s behind the host after 5 minutes on a loaded host (the time of the system, from the cycle counter, ran slow), with a date read one time: date now reads the clock again each 10 s and writes the page again, and the log gives each move of the date. The date of a version replaces its time since the format for the retention rule, so a machine that was off for a day ages its versions by a day.
  82. 456. kerneltool test tcp runs the two TCP machines alone, as test power and test main run theirs.
  83. 457. The corrections of the independent read of the versions and the date (written, not yet built or run by the author). The counts of names of a block are 32 bits, and a commit that would pass that type, or take a count below 0, is refused; the second case is a fault of the engine, and the store takes no change until the next start. A damaged version index or leaf no longer locks its object: the next commit of the object drops those versions. A full version index compacts its leaves. The open takes its recount of each store as the count of the store, and marks damaged an index, a leaf or a state whose count the format does not permit; a fault of the engine found at the open stops the changes of its store. The age of a version is the smaller of its age by the date and its age by running time. A reader of the date page gives up after 64 reads. The clock is decoded with each field checked, a window of 2020 to 2099 on the year, and a bound on each read. One commit refuses more than 65536 changes of counts or 1024 writes with its own error (Full, unit Commit_Blocks). The disk format does not change: the counts are in memory only. Host tests: support/storage_mount (a hostile disk, the recount, a count above 65535, the compaction and a model of a long run and of 30000 restores), support/storage_retention (the ages), support/calendar (the clock), contract/date (a writer stopped in a change).
  84. 458. A line record in the draw table (Line, draw records version 4, the byte reserved becomes line_thickness). The pixels are those of graphics.line, and a line is always stored from its left end, so a line and its reverse draw the same pixels. The damage is the bounding rectangle: pieces of the path would be joined again by the damage list, because they touch. A thickness of 0 gives no record; a thickness past 32 is cut to 32; an end past 32 768 pixels gives no record. rings uses it. threads, workers and processors show the pixels of their threads with a picture record. crowd and notes lose a margin and an empty band.
  85. 459. The corrections of the independent read of the interface package.
  • A dragged window keeps 8 pixels of its title on the screen at each edge; a window wider than the screen starts with its close button on the screen (support/bar/window_place.odin).
  • A new font reaches both pictures of a surface: draw_whole_window_next, also in the terminal and the manual.
  • The compositor uses a surface handle only when the kernel proves that the client gave it through its own ring (Handle_State with the ring memory), and closes it on each return. The kernel already had the proof: no change of the kernel or of a contract.
  • The present is the whole surface when the table of the shown picture is not known. A lost present makes the next frame of its picture whole.
  • Each edge of a record is cut, then its size is computed. refused is apart from full. grid_valid cannot wrap.
  • The keys hint of the terminal does not run over its status, and the status bar cannot wrap.
  1. 460. The other primitives come from the same origin, with the same proof: Poly1305, ChaCha20-Poly1305, SHA-2, HMAC, HKDF, X25519, Ed25519 and AES-128-GCM, 37 files in all. Where the origin chooses a hardware implementation by the processor (core:sys/info), a file of the system takes the place of that choice with the procedures of the generic file of the origin, unchanged: the portable code runs. compatibility_hash takes the place of core:crypto/hash with SHA-2 only. HAS_RAND_BYTES is false: a key comes from support/random.
  2. 461. The cryptography is the module cryptography. Its exports have long names (seal_with_chacha20_poly1305). The caller supplies each byte, and a failure is an error record. A tag or a signature that does not verify gives the new error kind Not_Authentic (32): no byte is given, and the count is negative, so no caller can take it for a success. Each copy of a secret in the module is erased before the export returns. The code runs with a zero context: runtime.default_context() stores the default random generator of the runtime, whose state is thread-local, which a module does not have. One call of an export costs 4 to 12 ns, a seal of 1200 bytes 2.4 us (ChaCha20-Poly1305) or 11.2 us (AES-128-GCM).
  3. 462. The seam of the batches. The batch exports wait for the tables as parameters of work/tables. Until then support/quic calls the cryptography through one procedure table of its own, so that a batch export later changes that table only.
  4. 463. The corrections of the read of the cryptography (review_cryptography.md). F1: an open read the sealed text two times (the tag check, then the decryption), so a writer of that memory got a forged text accepted; the open now copies the sealed text into the result once and opens in place, with the tag in the module. F2: ed25519_sign hashed the message two times and read R back from the result; it now signs a copy of the message (4096 bytes at most) into the module. Tests: an_open_while_another_thread_changes_the_sealed_text and a_signature_while_another_thread_changes_the_message (modules/cryptography, a second thread flips a byte during 200 000 opens and 20 000 signatures). The other copies (nonce, additional data, info of HKDF, outputs) and the headers read once at the entry (W5); an open into a result with no room is Too_Small (W2); a result that overlaps an argument with an offset is Bad_Value (W3); X25519 gives Bad_Value for a point of small order (N3); erase_stack_below_the_caller erases 16 KiB of stack after each export, a refill and a hash of a key (W4); one generator for each thread, with a check that fails closed (W6); the strength of the seed goes with each seed, in the context value 15 (W7); the check of origin walks the folders and checks the files of the system by their hashes (W1); root stops with a sentence if its seed is zeros (N7).
  5. 464. QUIC packet protection is a library in the program, over the module cryptography (docs/QUIC.md). support/quic reads a header as hostile input, derives the Initial keys from the Destination Connection ID, derives packet keys from a traffic secret, seals and opens a packet in place, applies header protection, and checks a Retry integrity tag. It calls the primitives through quic.Cryptography, the procedures of the module that the program fills from its import slots: the one seam where batch exports will come. Each datagram is copied once into private memory before any byte of it is read. Two exports join the module for header protection: header_protection_mask_with_aes_128 and header_protection_mask_with_chacha20. AES-128-GCM protects the Initial packets, as RFC 9001 asks; ChaCha20-Poly1305 is the preferred suite after the handshake, because its portable code is 4.6 times faster here. No kernel change is needed for QUIC. The tests are RFC 9001 appendix A (A.1 to A.5) and RFC 9000 appendix A, whose values a script copied from the text of the RFCs, and hostile datagrams.
  6. 465. A program chooses the least seed strength that it accepts (owner, 2026-11-11: "je pense que les fonctions qu'on expose devrait permettre de choisir ce que le programme tolère et dans le cas où les requis que le programme attend ne sont pas satisfaits et ben dans ce cas là c'est une erreur c'est pas grave"). random.fill_for_a_key takes the least strength (weak, processor, entropy) and gives the new error kind Seed_Too_Weak (33) and no byte when the seed of the program is weaker. A plain draw stays as it is. The module cryptography draws no random byte. QUIC and TLS ask for processor at least.
  7. 466. TLS 1.3 for QUIC, with raw public keys (docs/QUIC.md, section 4). Both sides as one state machine over bytes of the caller, with no allocation: X25519 and Ed25519 only; ChaCha20-Poly1305 first and AES-128-GCM also; one ALPN; the transport parameters as bytes; no resumption, no 0-RTT, no HelloRetryRequest, no client certificate. The server proves an Ed25519 key in a Certificate that holds its SubjectPublicKeyInfo only (RFC 7250); the client compares it with the key that its caller pinned: no X.509, no certificate authority, no date. Each message of the peer is hostile input, and each failure gives the alert of TLS and an error record. The key schedule is tested against RFC 8448, section 3.
  8. 467. The corrections of the second read of the cryptography (review_cryptography_second.md). B1: a ChaCha20 header protection sample that starts with ff ff ff ff asked the origin for the block at the counter 0xffffffff, which its limit check refused, and the program stopped; the coordinator makes one listed adjustment of the copied _chacha20/chacha20.odin (the last block is allowed), and the tests cover that block and that sample. F3: erase_stack_below_the_caller writes from the top down, so a short stack stops at its guard page before any other mapping is written; a thread that calls the module or draws random bytes needs 24 KiB of stack. W10: the proof of origin refuses a symbolic link and a file that no list can name. W11: the module takes 2048 bytes of additional data (a header with its token). W12: the open of a packet has two steps, so that the caller chooses the keys from the key phase. W14: the seal writes the packet number and checks the Length. W15 and W9: the missing tests. W13 (the size of the erasure) waits for a measure by the coordinator.
  9. 468. The transport of one QUIC connection (docs/QUIC.md, section 5). A Connection record in memory of the caller, with no allocation: the three packet number spaces, ACK frames, loss detection by the packet and time thresholds and the probe timeout of RFC 9002, a fixed congestion window of 12 000 bytes (NewReno comes after), flow control, one bidirectional stream that the client opens, the idle timeout, the anti-amplification limit of a server, the closing and draining states, and the handshake driven by the TLS state machine. Each frame of the peer is hostile input, with the error code of RFC 9000. NEW_CONNECTION_ID, RETIRE_CONNECTION_ID, RESET_STREAM and STOP_SENDING are minimal; Version Negotiation, Retry, key update and the stateless reset are not done. The tests run two connections in memory over a channel that the test controls.
  10. 469. The corrections of the third read of QUIC (review_quic_third.md). F5: a packet whose data has no place in a range set is not acknowledged, so the peer sends the data again; a sent packet whose data has no place stays in flight. F6: a server takes a client only with an Initial that opens with keys derived into local records, and a handshake that does not complete within 10 s ends the record. W16: a floor of packet numbers, under which each packet is refused. W17: times saturate, and the ack delay of the peer is at most its max_ack_delay. W19: the transcript holds 8192 bytes. W20: handshake bytes at a level whose keys were replaced are a PROTOCOL_VIOLATION. N15 and N17: tls_start and the start of a connection refuse a least seed strength under processor, and a client with no pinned key. W18 (the size of the stack erasure) waits for the measure of the coordinator; docs/QUIC.md says where a Connection record lives.
  11. 470. The table of error details (owner: "l'idée d'une table de détails c'est exactement ce que j'avais en tête c'est optionnel"): an error record stays 56 bytes, and a caller that wants each thing that went wrong gives an optional Append table of error records, the last parameter of an export that has an error record (MODULE_CONTRACT.md, section 8). An error detail is an error record, so each sentence writes it; the error record of the call is the first detail, so a caller with no table receives the record of before. A callee with nil stops at the first problem, as before: one test of a pointer. The three ways use the Append table as it is; after a failure the caller copies back the details and no other table. The loader gives load errors as details (its own form): each import that is missing or has the wrong hash. The call from the host carries the details in the bytes of a failed answer (Request.details_end, Answer.details), and the tool reads whole records only, at most the count. Host tests: the same details in the direct and the isolated way, a table too small keeps the count, the details of a load in its order, a count of the answer that lies. The machine test: monitor.counts_named with two rows that name nothing gives "detail 2 of 2", and imports loads lonely with a table.
  12. 471. One vector form: SSE2 (docs/SIMD.md): a procedure that computes with vectors has a scalar form and an SSE2 form. Every x86-64 processor has SSE2 and every kernel saves its registers, so there is no test of the processor and no choice at the start of a program: on amd64 the public procedure runs the SSE2 form, on another target the scalar form. An AVX2 form of each procedure behind one choice at the start of a program was written, built and measured on the host (load 10, so the times only rank the forms): AVX2 was slower than SSE2 for the blend of a glyph (244 against 193 ns) and for BLAKE3 (383 against 200 us for 390 624 bytes), and its blend of a glyph gave wrong bytes in the build with no optimisation. It was removed. AVX2 comes back only after the kernel saves the extended state, with a form that wins by measure on the machine and the cause of the wrong bytes understood.
  13. 472. A vector form gives the bytes of its scalar form for every input: the same integer arithmetic in lanes, no other rounding, no load or store past the slice of the caller (the last values of a row go through the scalar form). Host tests compare the SSE2 form with the scalar form on inputs from a fixed seed: every length from 0 to 67, every place against 16 bytes, the edge values; a failure gives the first wrong pixel, both values, and the pixels changed outside the drawn columns.
  14. 473. The blend of a glyph in SSE2 (support/graphics/blend_vectors_amd64.odin): 4 pixels at a time, one lane of 16 bits for each byte; the squares, the products, the index and the choice of each channel and pixel are vector code. The table of square roots stays, read one byte at a time (SSE2 has no gather). The comparison found a fault of the scalar form: a first pixel 0xFFFFFFFF was blended as black (no canvas holds it); it is corrected.
  15. 474. The blend of the pointer picture in SSE2; the fill, the copy and the swap of red and blue are not written again: the copy is runtime/memory.asm, and the compiler already makes the fill and the swap vector loops (seen in the code of the compositor).
  16. 475. BLAKE3 with four chunks side by side (contract/blake3/chunks_side_by_side_amd64.odin): a subtree of 4 chunks has its chunks compressed in the lanes of 16 SSE2 vectors, with a transpose of the words of each block; the parents and every other part of the tree stay scalar.
  17. 476. No SSSE3, SSE4.1 or AVX-512 form: their gain is a few instructions in loops whose cost is the reads of the table or the rounds, and each needs a test of the processor (docs/SIMD.md says what AVX-512 needs in the kernel). The arithmetic is base:intrinsics on #simd types, and a module stays compiled for the baseline. A lesson recorded for a later AVX2 form: a vector of more than 128 bits passes between a procedure with the target feature and one without it only through a pointer.
  18. 477. The line editor is a package with no state (support/line_editor): a Line record (200 bytes, a count, a caret) in memory of the program, and procedures that take one key and change the record. Each procedure brings the count and the caret back inside the bytes first: a record that the caller wrote wrong cannot make it write past an array. The package has no kernel call and does not know the codes of a keyboard (Edit_Key), so host tests run each key. The console keeps the line, its history and its list; the terminal still has no line editor (choice 90).
  19. 478. The completion list is a fuzzy match with a score (docs/CONSOLE.md, section 3): the characters of the query in order, gaps allowed; +16 a character, +32 at the start of the name, +24 after . or _, +16 side by side, -3 and -1 a byte for a gap; a tie goes to the tie order (the age of a history line), the shorter name, the order of the bytes. One pass forward and one back give the shortest window that ends at the first full match: bounded work (320 steps for a name), not the best window. The test pins the order of five queries over the exports of six programs, so a change of a rule shows.
  20. 479. The list opens while the person types, at the first character of a name, at once after a ., and inside the quotes of the first argument of load, isolate, start, replace, connect, stop, grants, revoke, unload. Tab or Enter takes the chosen name (a command gets its (, a name in quotes its closing quote), Up and Down move the choice, Escape closes the list until the next character. One name equal to the query closes the list. Enter on a name that is already the name at the caret runs the line: a line that ends with a variable (t, then Enter) runs as before. The common start of several names is not completed: the list replaces the second Tab.
  21. 480. The console sends the rows; the terminal lays them out (records List_Row and List, and Hint for the line under the edit line). The console knows the names from the descriptions; the terminal owns the cells, the width and the place of the edit line. A list record names a byte of the edit line, not a place on the screen (choice 90). The rows go through the text region of the console and its terminal, which no third program reads. The terminal draws each byte under 32 or over 126 of a list or a hint as ?.
  22. 481. A cell of a text grid has a fill color (filled, background; draw record version 5): the list needs the card color under its text and the blue of the title bar under the chosen row, with matched characters in another color, which reverse cannot give. A cell is 12 bytes in place of 8: the view of the terminal (32 x 104 cells) and its two copies grow from 26 624 to 39 936 bytes each, and the difference reads 50 % more bytes for each frame. The border of the list is four records of one pixel after the others.
  23. 482. The bounds of one key: a name is read for 128 bytes at most, a query of more than 64 bytes matches nothing, one ranking reads at most 4096 names and keeps the best 64; the console reads at most 512 names (its tables are smaller). One key costs at most about 1.3 million steps. The console measures each ranking with the cycle counter and writes the longest one when Tab opens a list.
  24. 483. The line history is a ring of 16 384 bytes for at most 256 lines (line_editor.History): a line takes its own length, and never wraps at the end of the ring, so each line is one slice. The same line two times in a row is kept one time. Up and Down walk it, and the line that the person typed comes back after the newest. The history search is the completion list over the history: Control-R opens it and moves down (older), Control-S moves up (newer); Control-S with no list does nothing; Enter puts the chosen line in the input and does not run it; Escape keeps the line as it was. A line that the history holds two times shows one time in the search (the newest).
  25. 484. The modifiers reach the program of a terminal: the input service, the compositor and display.next_event carried them, and the terminal dropped them. The key record of the terminal has them in values[1] (terminal.Key.modifiers). The console types no character for Control with a key.
  26. 485. The completion writes on the log only when the person asks (Tab that opens a list, a take of the list or of the search): the main scenario has about 13 log records of room before the program log reads the oldest ones, and each line that it types opens lists.
  27. 486. The completion of a name in quotes uses what the console holds: the boot image for the modules, Inspect on the machine for the programs that run. The root service has no command that lists its services, and the console asks for no new grant: connect(" lists the programs that run, and the root service still decides. With no grant to inspect, the list names the services that the console is connected to.
  28. 487. The line history across a stop is designed, not built (docs/CONSOLE.md, section 6): one object of the private store of the console (it holds the grant Store), written at most each 10 s and at shutdown(), read at the start as data that the console did not write; nothing runs on load; a command empties it.
  29. 488. The fast blend of text over a flat color (graphics.blend_glyph_rows, docs/FONTS.md section 5): blend tables of 3 x 256 results for each pair of a text color and a pixel color, 8 tables in 24 KiB of the program, each value computed on first use by the formula of the reference; a row of a glyph whose two ends have one color uses its table. Measured on the host (taskset, the least of 3 000 batches, the least of four cores): 43.4 of the 128 words of a cell are partial; one glyph at scale 1 over a flat color 109 ns against 181 ns for the reference blend and 33.8 ns for Spleen (3.2 times, 5.4 before); over a gradient across 178 ns. A host test compares each pixel with the reference: 0 differ.
  30. 489. The settings survive a stop: one record of 48 bytes (settings.Kept_Settings) in one object of the private store of settings, written before the page; a boot pass of settings (no window) reads it and writes the page. The storage service now starts at boot. The boot display uses the defaults: the root service draws it before the storage service runs.
  31. 490. The loss of power for the settings (kerneltool test power): 1000 records, one commit each; 306 images, 72 torn, each gives the durable record (227) or the record in flight (79), never a mix; the next boot pass reads the durable record.
  32. 491. Two more settings, one row of a table each (settings.descriptions): the pointer theme (the compositor receives both theme files and chooses live) and the grid fit (two glyph caches in the boot image, +397 344 bytes). The scheduling strategy is not a setting: the right Schedule stays with scheduler (P20); the person changes it there.
  33. 492. A bold weight for the titles of the cards: the ASCII subset of JetBrains Mono Bold (14 212 bytes), a glyph cache at scale 1 only (30 720 bytes); the draw record version 6: byte 3 is kind_detail, the thickness of a line (choice 458) and the weight of a text.
  34. 493. The window of settings is not covered in font_vector: the scenario moves monitor to the left and settings to the right.
  35. 494. The context values of the settings: the boot pass of settings is the value 17, and the second pointer theme of the compositor the values 18 and 19 (CONTEXT_VALUE_SETTINGS_BOOT_PASS, CONTEXT_VALUE_SECOND_POINTER_THEME_ADDRESS, CONTEXT_VALUE_SECOND_POINTER_THEME_SIZE in contract/operation): the values 9 to 16 are the random seed, the settings page, the zero address of fault, the date page and the strength of the seed.
  36. 495. Paper Mono is the font of the system (decision of the owner: "Paper mono très bien pour monospace"; docs/FONTS.md, section 6): the default vector font (DEFAULT_VECTOR_FONT paper_mono) and its SemiBold for the titles (EMPHASIS_VECTOR_FONT), fetched by the flake from its repository at one commit with the hash of each file (it is not in nixpkgs). Its advance is 606 of 1000 units: an em of 13.2 pixels on the grid of 8 x 16; the baseline is row 12, its capitals 9 rows. i and j are glyphs made of glyphs, which the subset makes simple. It has the box-drawing characters; the glyph cache keeps ASCII only in this step. JetBrains Mono stays a candidate and the font of the host tests of the renderer.
  37. 496. One table of the fixed ranges (contract/fixed_addresses): the address and the span of each mapping that the system gives a program (the files of a module, the pointer themes, the settings page, the three glyph caches, the date page, the storage file, the tables of an export), one slot of 1 TiB each from 0x3000_0000_0000; the compiler checks that no two overlap. Each owner takes its address from it. The storage file moves from 0x3100_0000_0000, where the pointer themes also were, to 0x3700_0000_0000. Two pages that a program places by its own choice move out of the band: the measure page of errors (it was at the address of the settings page) and the objects of a locker (at the address of the module files).
  38. 497. B612, the interface font, as subsets (decision of the owner: "Go b612"; docs/FONTS.md, section 12): Regular and Bold from its repository at one commit, with the hash of each file; the subsets keep the 232 and 239 pair adjustments of kern between ASCII characters. The reader of kern (version 0, a first subtable of format 0) checks each count and offset, the order of the pairs, and each glyph; the reader of hmtx now checks its length for every glyph. Host tests: the advances of 12 characters and 7 pairs against the values read by hand, a damaged kern table.
  39. 498. The glyph cache version 2, one format for monospace and proportional fonts: positions for each scale, a box for each glyph, the advances and the pairs in 1/64 of a pixel; B612 Regular with 3 positions at scale 1 and 1 at the scales 2 and 3 (408 524 bytes), B612 Bold at scale 1 with 3 positions (100 816 bytes), measured before the cap height came from OS/2. The size on the grid: the cap height that the font states in OS/2 (1 500 units) is 10 rows at scale 1, not the top of the outline of H (1 520, the angled tops of its stems). Each advance and each pair is rounded to 1/64 of a pixel on its own, to the nearest, a half toward plus infinity; the shaper adds the integers. validate checks each new part; host tests damage each one.
  40. 499. The text shaping module (decision of the owner: "Il nous faudra un module de text shaping/kerning"): support/text_shaping and modules/text_shaping, exports shape_text and measure_texts over tables of the caller; the spacing is two tables of the caller; each value read once and kept in its limits; a character outside the cache is placed as ?.
  41. 500. No program chooses where another program reads its glyph caches (review_fonts_hostile.md, Fault 1, older than the fonts branch). The settings page loses the addresses, the sizes and the hash of the glyph caches: the program settings writes that page, and could point each later program at memory that it controls and change it after the validation. The address of each cache is its fixed range; its size is a value of the context that the root service writes (20, 21, 22), bounded by the span of the range (settings.glyph_cache_place). Each cache mapping is read-only for every program, and the one handle of the boot image has no right Write: no program writes a cache. A reader of the page gives up after 64 reads of a page left in a change and takes the defaults. The window of settings shows the hash of the font from the header of its cache.
  42. 501. A budget of 64 parts for a glyph made of glyphs, in all its levels (COMPOSITE_PART_LIMIT; Weak point 1): a glyph of 21 000 empty parts in four levels ends at once. A face number past a collection is the problem No_Face.
  43. 502. A monospace glyph cache keeps each glyph in its cell (Weak point 2): the header has FLAG_PROPORTIONAL; with no such flag, validate requires one position, no pair, left 0, width at most the cell, and the advance of the cell. validate also refuses an unknown flag or filter and a font name that is not printable text with an end. The kernel tool validates every cache that it writes.
  44. 503. The text over a gradient or a picture takes the SSE2 form (Weak point 3): the fallback of the table blend is the form of the target. A host test compares the table blend, the SSE2 form and the scalar form byte for byte with Paper Mono, its SemiBold and B612; a host speed test gives the three over a flat color, a gradient across and noise, for the choice of one path.
  45. 504. The fixed ranges have one place: the enumeration Fixed_Range; each address is its slot. The loader refuses a module that passes BAND_START. The places of address_space are checked against OWN_CHOICE_START at compile time. Paper Mono states the cap height of its outlines (710, x-height 509): its path stays.
  46. 505. The mark of the system is data in support/mark (decision of the owner: "le heron A n°1 est le meilleur, nickel cest simple ! + le logo \"modus\" comme t'as fait super cool"): the 24 rows of the heron, the table of its parts with their colors on the dark and on the light background and their boxes, the letters of the name of the mark, and pure procedures of one pixel. Each part takes one linear gradient from 30 % toward white at the top left of its box to 30 % toward black at its bottom right, taken at the centre of each pixel, in integers rounded to the nearest value: at most one unit from the gradient in floats of the proposal. kerneltool logo writes resources/logo and .project/image.png with the PNG writer of the Odin standard library (compressed, with alpha); kerneltool check compares the pixels of each file with the data, not its bytes, because the bytes of the compressed stream depend on the Odin release. .project is in the files of the check of the flake. Host tests: the box of each part against the rows, pinned pixels, the cells at cell size 1, two calls with the same pixels, the cells of the name.
  47. 506. A listen grant of UDP (docs/NETWORK.md, "The listen grant of UDP"). The datagram contract had a listen grant for TCP only. A datagram endpoint can now listen on one local UDP port that the root service names, and answer the remotes that came to it, with the table of admitted remotes of TCP unchanged. A new remote is admitted on a datagram of 1200 bytes at least (LISTEN_FIRST_LEAST, the least first datagram of a QUIC client), from an address that can answer, when a Receive waits. No kernel change.
  48. 507. A QUIC server of several connections (docs/QUIC.md, section 6; support/quic/server.odin). A table of 16 connection records in a mapping of the program. A datagram goes to the record of its Destination Connection ID and its remote; an Initial with a new identifier takes a record only when a copy of it opens, and under the limits: 8 connections and 2 handshakes for one remote address, 8 handshakes at once. A datagram over a limit gets no answer. A long header of another version gets one Version Negotiation packet, 16 in one second at most. The server sends disable_active_migration. Not done: Retry, the stateless reset.
  49. 508. quic_echo, and the key of a server (programs/quic_echo). An echo server over the listen grant of UDP 4433, with the module cryptography through its imports. It makes its Ed25519 key at its start from its own generator (strength "processor" at least), writes its public key on the log and in its window for a client to pin, and keeps the private key in its memory; a new start makes a new key. A weak seed makes no key and no endpoint, and the window says so.
  50. 509. The QUIC client of the peer, and kerneltool test quic (tools/kerneltool/peer_quic.odin, quic_scenario.odin). The peer links support/quic and the module compiled for the host, pins the key that the machine printed (step Quic_Key), and runs cases (step Peer_Quic): 1 MiB each way, with loss, reorder and duplicates, 8 clients, a wrong pinned key, a flood of 1000 forged Initials from 198.18.0.0/15 with a datagram of another version, and a client that vanishes. A stack of another project (OpenSSL 3.6 s_client -quic -enable_server_rpk) and remote over QUIC are designed, not built (docs/QUIC.md, sections 6 and 7).
  51. 510. Retry: a server keeps nothing before a client proved its address (review_quic_fourth.md, F7; docs/QUIC.md, section 6; support/quic/retry.odin). An Initial with no token gets a Retry, and no record, no key, no open of the Initial. The token is ChaCha20-Poly1305 under a key that the server makes at its start, over the original identifier of the client, the identifier of the Retry and a time, with the address and port of the client as additional data; it lives 10 s. A record is taken only for an Initial whose token opens for its remote and names its identifier. A client takes one Retry, checks its tag, and checks original_destination_connection_id and retry_source_connection_id. A replay of a token works only from the address and port of its client, for 10 s.
  52. 511. Budgets of the work of new identifiers (F8). In each second: 2000 Retries (2 exports each), 2000 checks of a token (1 export), 500 checks of an Initial with a valid token (11 exports): about 41 ms of one processor in the worst second. Over a budget, a datagram is dropped before any cryptography. The server copies each datagram once and decides on that copy (W23); quic_echo gives the datagrams of known records first in each pass.
  53. 512. The network service holds the limit of 3 times (W21; contract/datagram, programs/network). A UDP listen grant sends to a remote at most 3 times the bytes that came from it, until the program marks it with the new command Validate (status Amplification else); a send no longer keeps a place alive. A program that validates every remote lifts the limit for the addresses that sent it a datagram, for 120 s after their last datagram.
  54. 513. The other points of the fourth read: a NAT that changes the address or the port of a client ends its connection (W22, written in docs/QUIC.md); a record has a generation number for the state of its program (N23); the closing state answers the 1st, 2nd, 4th, 8th... datagram (N25); the tests send the flood of F7 (N28). kerneltool test quic-real runs openssl s_client -quic of the host against quic_echo through QEMU user networking, written and not run.
  55. 514. The UDP relay of the peer for kerneltool test quic-real (docs/QUIC.md, section 6; tools/kerneltool/peer_relay.odin). QEMU user networking with restrict=on drops each UDP datagram of the machine (libslirp, udp_input), and restrict=on stays. The machine of test quic-real has the network device of the peer, and the peer relays UDP between a socket of the loopback of the host and the UDP port 4433 of the machine, from 10.0.2.2 and a port for each client of the host (a table of 16). peer_run reads every frame that waits before the next sends of the cases. quic_echo logs the error code, the frame type and the reason phrase of each close. The test needs only the loopback of the host: it can join the whole test after a first pass.
  56. 515. One address cannot spend the shared budget of Initial checks (review_quic_fifth.md, F9; docs/QUIC.md, section 6; support/quic/server_budgets.odin). An Initial with a valid token passes, in this order, the check that its token took no record before, the limits of its address, of the handshakes and of the table, then a budget of its own address (4 Initial checks at once, 4 in each second, in a table of 32 addresses whose token opened), then the shared budget, and only then the open. Each token takes a record once: a table of 64 counters of tokens that took a record, kept for the lifetime of 10 s; full, it forgets the token issued first, which can then take a record again from its own address and port until it expires. A table of used counters, and not a window of bits over the counter: it keeps 1.5 KiB only for tokens that took a record, where a window must cover every Retry of 10 s (25 KiB) and a flood of Retries would push the tokens of real clients out of it.
  57. 516. The shared budgets are token buckets (W24). A rate and a depth of a tenth of a second: 20 000 Retries a second (2000 at once), 30 000 token checks (3000), 2000 Initial checks (200). By the computed costs (10, 3 and 30 us), every budget spent takes about 35 % of one processor; a flood must bring 24 MB/s with no token, or 36 MB/s with tokens, to spend one. Above that, new clients stop for the duration of the flood, and connected clients go on; docs/QUIC.md says so, and designs a budget for each source prefix that a deployment adds. The host test the_cost_of_the_work_of_new_connections prints the real costs on the host, for the coordinator to set the rates.
  58. 517. The other points of the fifth read. W25: quic_echo decides the order of a batch once (quic.server_batch_order, known records first), so the second datagram of a new client in one batch is given, to the record that the first one took. N32: that order reads the header in place, with no copy, because it is a priority and not a decision of security; each datagram is copied once. W26: a record asks for the validation of its remote at each datagram until a Validate for its generation gives 1 (quic.server_validation_confirmed; the tag of a Validate names the place of the record). N30: the tool requires at most one Retry to each forged address and port of the flood. N29: docs/QUIC.md, section 6, no longer says "not run yet". C1: no comment names a review.
  59. 518. The mark in the system (decision of the owner: "The mark in the system: the heron in the bar of the desktop and at the boot display"). The bar is 32 pixels high, so the heron fits at a cell size of 1 (24 pixels), at (16, 4), the height of the buttons; it takes the place of the word "kernel" and the buttons do not move. The compositor computes its gradients once, into a picture of 24 x 24 pixels that first gets the pixels of the bar from the same records, and shows it with a Picture record of a fixed generation: a frame pays a copy of 24 rows. The boot display draws the heron with cells of 2 pixels and the name of the mark with cells of 3 in its head, in the place of the large word "kernel": no card moves. support/mark draws into a canvas (mark_draw, name_draw, the covered pixels only, cut to the canvas, in the order of the channels of the screen) and refuses a cell size above CELL_SIZE_LIMIT (1 << 16), which kept a division from wrapping to 0 at a cell size near 1 << 52 (Weak point 2 of the second read of the fonts).
  60. 519. The compositor tells the windows of a change of the settings only when the values differ (Weak point 1 of the second read of the fonts). It keeps the values that it told last and compares them with settings.values_differ; the count of changes alone no longer sends a record into the ring of each window, which also carries its keys. The first change after the start is always told, as before.
  61. 520. The status line of the terminal names the lines of the text that the view shows ("lines 709 to 736 of 737", or "none in view"). It named the first row of the view as a line: after clear() the terminal forgets its lines and the first row is the edit line, so it said "line 738 of 737". The cause was not the hint line: it showed with or without one. The view counts the rows that show lines of the text (View.text_rows), and the status uses that count.
  62. 521. One free row under the edit line at all times: the hint line is drawn in it, so a hint that comes or goes does not move the text by one row. The cost: the view of a terminal with an edit line shows one line of text less. The list opens under the free row, or above the edit line when the rows below do not hold it, over the text.
  63. 522. A hint line has a long and a short form; the terminal chooses (terminal.hint_form_choose, a pure procedure with a host test): the long form when it fits in the columns, if not the short one, cut to the columns. The terminal knows its columns; the console does not ask for them, and a resize of a terminal needs no new record.
  64. 523. The measure of one key is a command of the console (measure_completion()), in the second thread with its own table of names: the collect, the rank and the rows, the least of 20 batches of 10 keys, for the names of the console and for 512 names of 128 bytes whose matches are far apart.
  65. 524. The names of the programs that run are read once for each quote: when the caret enters the quote of connect, stop, grants or revoke, and kept until it leaves the quote or a line runs. A key in the quote makes no kernel call.
  66. 525. The line history file (support/line_editor/history_file.odin): version 1, the lines oldest first with a byte of length each, a BLAKE3 hash at the end; the newest lines that fit in one block of 4096 bytes. The read checks each field and each byte before it adds a line; a file that fails gives an empty history and the name of the check. Nothing runs on load.
  67. 526. The console does not write the line history file yet (docs/CONSOLE.md, section 6): it holds the grant Store, but its store is the store of 4 blocks that shows the limit in the main machine, the storage service starts on demand, and each commit moves the generation that many checks name. Two forms keep the properties (a second store for the history, which is a new grant and a decision of the owner; or a larger limit for the console and the check of the limit moved to another program); each one changes those checks one time.
  68. 527. The console tests the failure of an isolated call first (review of steps 71 and 73, F1): on a failure it copies back only the table of error details (isolated.error_details_return), as system.call_isolated does; the rows that a failed export wrote in a Change table stay out of the variables of the console. Shown by the export scale_values_up_to of sample (it changes the readings in order and refuses the first one that passes a largest value): after its failure in a call host, sum_values(t) gives the sum of before (300, not 480).
  69. 528. The order of two names reads at most 256 bytes of them (NAME_COMPARE_LIMIT, W1), and the place of a name in the kept ones is a binary search (7 orders): one key costs at most about 8.9 million steps for 4096 names, whatever their length; before, the order read whole names, up to 64 times for each name. 256 and not 128: a line of the history (200 bytes) compares whole, so two lines that differ at their end stay two lines in the search.
  70. 529. The rows of a completion list go to one List record (W2): support/terminal keeps them (List_Keeping, list_keep_row, list_keep_show, list_keep_forget, pure procedures with host tests); the rows given are forgotten after each List and when the program stops, so a List with no rows shows empty rows. A terminal serves one program: the root service starts a new terminal with each start of a program.
  71. 530. A wrong table of error details keeps the error record of the export (W3): system.call_isolated gives the new status Failed_With_Wrong_Details, with the record that says why the export failed, and the count of the table of the caller from before the call. Before, the record was replaced by one about the table, and the reason of the failure was lost.
  72. 531. The hint line says when names were not read (note 4 of the review): more than 512 names gives "and more names than the list reads: type more characters".
  73. 532. Control-R is a character with Control, not a key code: the input service gives the character of the layout with Control held, so the search follows the letter R in any layout (docs/CONSOLE.md, section 1).
  74. 533. The zero of the system is the slashed zero of Paper Mono (decision of the owner, 2026-11-12: "yes" to "Take the slashed zero"). The feature zero of the table GSUB of both pinned files is a single substitution of the glyph 294 by the glyph 304 (zero.zero, 3 contours and 32 points against 2 and 24 or 26). The tool reads it with a reader of GSUB that takes each offset as hostile input, and draws it for '0' in the subsets of Regular and SemiBold; a font with no such feature keeps its zero, and the tool says so. Every picture with a 0 changes.
  75. 534. The text of a proportional font is drawn (graphics.text_proportional, docs/FONTS.md section 12.8, step 4): each placed glyph at the nearest third of a pixel, through the blend of the text of cells; the ink of a text stays within its measured box and 2 pixels on each side (200 texts); the three blends give the same bytes. The program interface_font shows B612 Regular and Bold, three pairs with and without their adjustment, one word at the three positions, the digits in columns, and the time of one line; it builds its two caches at start. The caches as resources of the boot image wait for a decision: the context has one free value of the system (23), and two sizes need two.
  76. 535. The text of cells refuses a proportional cache (graphics.cell_cache_problem, the problem Proportional of contract/glyph_cache): text_cache_use, text_bold_cache_use and display refuse it; the text falls back as for any refused cache.
  77. 536. The limit of the rule of the glyph caches is written: a program trusts its starter, which writes its context and chooses its mappings; under the root service, the starter of each program with a surface, no program writes a cache or points a program at other bytes (docs/FONTS.md, section 5).
  78. 537. The loader stops at an invalid header, and its band rule is tested: place returns the error of module.header at once; module_fits_below_band is the rule (a module and the page after it end at MODULES_END, a new name of contract/fixed_addresses); a load whose module would end at the band is refused with the stage Room before any kernel call. kerneltool check refuses a file of contract, support, programs or modules, outside contract/fixed_addresses, that names BAND_START or SLOT: an address of the band has no other source than a member of Fixed_Range.
  79. 538. No size of a glyph cache goes through the context (decision of the coordinator, 2026-11-12; the owner can reverse it). The value 20 is a set of bits, one for each member of Fixed_Range: the ranges that the starter mapped (CONTEXT_VALUE_FIXED_RANGES_MAPPED, fixed_addresses.ranges_mapped). A program reads nothing at a range whose bit is clear. A cache states its own size in its header: glyph_cache.validate_at bounds it by the span of the range and by the header, then validates the whole. The root service gives a cache only when validate accepts the resource, so the header is mapped and states the size of the resource. The values 21, 22 and 23 have no role.
  80. 539. The settings page and the date page are stated by their bits: the values 13 and 15 held only their fixed addresses, so their bits remove two values and change nothing else. The pointer themes, the storage file and the files of a module keep their values, which hold sizes; the tables of an export are mapped by the receiver at each call.
  81. 540. The two caches of B612 are resources of the boot image (interface_glyph_cache, interface_glyph_cache_bold), at the new fixed ranges Interface_Glyph_Cache and Interface_Glyph_Cache_Bold, mapped read-only for each program with a surface, each terminal and the compositor; display.interface_font_open validates them; interface_font uses them and builds no cache. The tables of mappings of the root service grow to 12 for a program and the compositor and to 8 for a terminal.
  82. 541. The table of address budgets gives at most the sum of its buckets (review_quic_sixth.md, F10; docs/QUIC.md, section 6; support/quic/server_budgets.odin). A place of the table moves to a new address only when it is free or its bucket refilled to its depth; when no place can move, the new address gets no Initial check (counter address_table_full). The table has 1024 places of 32 bytes (32 KiB), and each address 4 checks at once and 1 in each second: addresses that rotate through it get at most 4096 checks at once and then 1024 in each second, half of the shared budget of 2000. Before, 33 real addresses in turn spent the shared budget. What remains: more than 1024 real addresses that keep asking hold the table. A place frees only when its address asks less often than the rate (1 s after a client that needed one check), and the first new address to ask takes it. A new client then has no bound on its wait while such an attack lasts, and gets a place within 4 s after it stops. The budget for each source prefix (designed) is the next defence.
  83. 542. The smaller points of the sixth read. server_batch_order reads the header of each datagram one time (the known datagrams fill the order from the front, the others from the back). The tag of a Validate of quic_echo carries the place and the low 27 bits of the generation of the record, so a result for an older record of a place confirms nothing. The cost of the work of new connections is computed; the speed test exists, and its numbers of a quiet host are not yet in docs/QUIC.md.
  84. 543. Several streams in a QUIC connection (docs/QUIC.md, section 5; support/quic/streams.odin). The caller gives a table of stream records with each connection; their count bounds the streams open at one time. Only the client opens streams, all bidirectional. A server gives the client a limit of streams equal to its records, and one more each time a stream ended on both sides (MAX_STREAMS), so the limit never promises a stream that has no record; a client at the limit sends STREAMS_BLOCKED. A frame that names a stream of the client opens it and each lower one, within the limit. Flow control for each stream (64 KiB) and for the connection (the buffers of every record). RESET_STREAM and STOP_SENDING both ways, with an API for the application (stream_reset, stream_stop_sending). Each packet carries one STREAM frame, of the streams in turn. quic_echo gives each client 4 streams and echoes each one; its budget grows to 4096 pages. The tool has the case "streams": one client, 4 streams of 256 KiB at once.
  85. 544. remote over QUIC: one call, one stream (docs/QUIC.md, section 7; designed, not built). Each call is a bidirectional stream of its own: the request header and the call region, then the end; the answer header and the result, then the end. The request number, the answer parts and the limit of one datagram leave the contract. 8 calls at once for one host; remote runs one call at a time in the order in which the requests completed. An error of one call is a reset of its stream with a code of a new enumeration Stream_Error, never a close of the connection.
  86. 545. A reset states the bytes sent (review_quic_seventh.md, F11; docs/QUIC.md, section 5; support/quic/streams.odin). The final size of a RESET_STREAM of this side is the largest offset that it sent, not the bytes that the application wrote (RFC 9000, section 4.5): at the reset, the stream forgets the bytes that flow control held back. Before, a stream reset after flow control held bytes back gave a final size over the limit of the peer, and a correct peer (this library too) closed the connection with FLOW_CONTROL_ERROR; the two sides also counted different bytes for the connection. The receive side was right: a final size over the limit of the stream or of the connection is FLOW_CONTROL_ERROR. A stream record writes zeros over the part of its rings that it used when it is freed, and when a new connection takes a record that an ended connection left open (N38). The rule that a record is freed only when the application read the end of the stream is written where stream_read is and where quic_echo takes its streams (N40). docs/QUIC.md says that exactly 1024 real addresses hold the table of address budgets, at about 1.2 MB/s; choice 541 said "more than 1024".
  87. 546. The kernel saves the extended state with xsave, and programs can use AVX and AVX-512 (owner, 2026-11-11: "Nah évidemment qu'il faut donc ajouter le support mdrr. On va pas se faire artificiellement limiter par fxsave"; ARCHITECTURE.md, "The extended state of the processor"). The kernel sets CR4.OSFXSR, CR4.OSXMMEXCPT, CR4.OSXSAVE and XCR0 itself on each processor; a bit of XCR0 is set only for a component that the kernel saves: x87, SSE, AVX, and the three components of AVX-512 when the processor has them; not MPX, PKRU or AMX. The area of each thread is a fixed field of 2688 bytes in the thread table of the kernel (not pages of the budget: the slot of a thread already pays for its frame). xsaveopt64 at each entry from a program and xrstor64 at each resume, with all components: a program that never uses AVX pays no write and no read of the ymm high halves. The trap of the modified optimization of xsaveopt (an area changed by another writer between the restore and the save of one processor) cannot happen: a processor saves only into the area that it restored last, and the kernel writes an area only before the first resume of its thread. With no xsave or no xsaveopt, fxsave64 and fxrstor64, chosen once at start and logged: one compare at each entry and resume. A new thread gets the start state: x87 control 0x037F, MXCSR 0x1F80, XSTATE_BV 3, everything else zero; the first processor restores it and saves it again at boot. A program reads its usable instruction sets with support/instruction_sets (cpuid and xgetbv as asm templates), with no kernel operation. The processors of the test machines: Processor_Model in the tool replaces the flag of the random test.
  88. 547. The corrections of the independent read of the extended state (review_xsave.md). W1: the tool reads whether the host gives the machine AVX-512 (avx512f, avx512bw, /dev/kvm) and waits for the exact line of that machine; the host of the tests has none, so the AVX-512 path is not tested there (a gap). W2: in each pass, one round changes only MXCSR and one only the x87 control word, between two entries; with AVX-512 BW the opmasks are moved and checked whole (kmovq). W3: the boot proof restores values that are not the init state and reads them back (fnstcw, stmxcsr, xmm0, and the high half of ymm0 with AVX), so it proves the offsets; the proof of the start state says that it only shows that the start state does not fault. N1: vzeroupper after the save at an entry when AVX is enabled, chosen by the form byte (Xsaveopt_And_Vzeroupper), with no other read. N3: the form byte is in the processor record at offset 32, in the cache line of the area pointer that the entry reads already, written once by its own processor; the byte in the data of the kernel is gone. N4: the log line says what one save writes at most and the area of 2688 bytes of each thread entry. N5: thread_create writes the thread entry in place (a zero fill, then each field), with no temporary of the size of the entry on the kernel stack. N7: the test takes up to 31 threads (the most for a program); a machine with more than 29 processors gets fewer threads than processors plus two, and the log says it.
  89. 548. The kernel saves and restores the extended state only at a switch of threads (ARCHITECTURE.md, "The extended state of the processor", section 3). It replaces, in 505, "xsaveopt64 at each entry from a program and xrstor64 at each resume", and in 506 the parts N1 (vzeroupper after the save at an entry, the form Xsaveopt_And_Vzeroupper) and N3 (the form byte in the processor record). The reason is measured: one kernel entry of locks took 177 ns on master (fxsave64 and fxrstor64 at each entry and resume) and 229 and 249 ns with the save at each entry by xsaveopt64 and xrstor64; on the host the pair is 227 cycles with fxsave64 and 430 with xsaveopt64 (xrstor64 alone 327). Now schedule saves the thread that a processor leaves, before the thread can reach a run queue, and restores the thread that it takes, just before run; an entry, an interrupt and the return to the same thread touch nothing. The entry and the resume of kernel/processor.asm lose the save, the restore and the read of the form; the processor record loses extended_state_area and extended_state_form (self is now at offset 16 of gs); the scheduler passes the form, Fxsave or Xsaveopt, to the two procedures. A thread taken again just after it left is restored again: an owner of the registers is a later step. The program extended_state measures one hand-over between two threads with x87 and SSE in use and with the ymm high halves in use.
  90. 549. The kernel has no x87, MMX, SSE or AVX instruction, and the build proves it (ARCHITECTURE.md, "The extended state of the processor", section 3a). A rule of the kernel: no code that runs in the kernel, an interrupt handler included, changes a register of the extended state, except the save and the restore. The kernel object is made by llc with -mattr=-x87,-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-avx512f,+soft-float (the IR of Odin has no target features on its functions, so these hold for all of it; the kernel has no floating-point value, and the four f16 conversions of the Odin runtime that the IR holds are not called and are dropped by the link). runtime/memory.asm has a kernel form with general registers and rep movsb/rep stosb (WITHOUT_VECTOR_REGISTERS), with its host tests in runtime/without_vector_registers. After the link, the tool disassembles the kernel with llvm-objdump and refuses any instruction that names a vector, x87, MMX or opmask register or uses that state, outside extended_state_save, extended_state_restore and extended_state_restore_and_read (tools/kerneltool/vector_check.odin, host tests in vector_check_test.odin). Built by the coordinator with the first form of the features: the linked kernel has no instruction that names a vector register outside the three procedures (635 on master). After the third independent read, the check cannot pass on a listing that it does not read: it fails with fewer than 10 000 instructions read (the kernel has 31 145), when one of the three procedures is missing, when it sees no vector instruction in one of them, or when a procedure is not followed by its end label (<name>_end, in kernel/processor.asm); it refuses wait (the name of fwait in the listing) and the instructions of AMX; and the build refuses kernel IR whose functions carry target features of their own.
  91. 550. The elements of the interface draw in two fonts (docs/FONTS.md section 12.8, step 6; docs/INTERFACE.md section 5): names, titles, labels, buttons, lists and column titles in the interface font (B612); values (numbers, typed text, the count of a card, the cells of a table, the text grid) in the font of the cells (Paper Mono). The draw record is version 7: the kind Interface_Text, its rectangle grown by its ink margin, its origin at a third of a pixel (text_origin_thirds, which was text_subpixel_x). Each element measures a text in the font that draws it and cuts it to its box. A program takes it with no change of its code; interface_elements and interface_through_module needed one line each.
  92. 551. The rule of the two fonts in DESIGN.md, section 5 replaces "one font at a time, on the grid of cells", as a proposal that the owner accepted in substance on 2026-11-11 ("Go b612", "Paper mono très bien pour monospace").
  93. 552. The module interface_module takes the interface font of the program that runs it: in a direct call, the caches at the fixed ranges of the caller; in an isolated call none, so the call host draws in the font of the cells. Hostile Interface_Text records draw nothing.
  94. 556. The line history has a store of its own: a decision of the coordinator (2026-11-12; the owner put the item in his list and did not choose; he can reverse it). The history is the state of the console, not a file of the person; the store of the person and its checks stay as they are. The root service gives a second connection to the storage service (Root_Command.Connect_History, the role History_Store, the grant history): the store console.history, no grant to act for the person, so that store and no other one.
  95. 557. The history store has 4 blocks, not 2: one object of one block takes 3 blocks of a store, and a change needs one more in the commit that thins the version before (the store of the person shows both). A hostile console fills 4 blocks at most.
  96. 558. The connection comes through the root ring at the start of the console, not in its context: the places of the store connection in a context are those of the terminal, and the storage service starts at boot after the console (for the boot pass of settings); the root service answers the ring after that start, so the order of the boot does not change. The console waits 30 s at most for it.
  97. 559. The root service keeps the history connection apart: Disconnect by the name of the service does not end it; a revocation of store does not touch it; a revocation of person gives it its limit only; revoke("console", "history") ends it. One connection of each kind for one program and one service.
  98. 560. One commit after each line that the history added, in the second thread while the line still runs; shutdown() and stop("console") save before they act. A held Enter adds one line at most. A rule of time (one write in a second) makes the count of commits a fact of the host, and the generations count the commits of the whole disk; a rule of lines keeps it a fact of the scenario.
  99. 561. The load checks the file as data: absent, damaged, unreadable, larger than 4096 bytes, or refused by history_file_read gives an empty history and one log line; a line keeps the bounds of a typed line; nothing runs.
  100. 562. The machine tests count generations from a base (the step Generation and {generation+N} in the text and the expected text of a step, generation_expand with a host test): the main machine reads the generation of the first write of the person, and names the 9 generations after it from that base; the persistence machine names 1027 in place of 1023 (4 commits of the history) and 3 stores, 4 objects in place of 2 and 3.
  101. 563. history_clear() empties the history in memory and removes its object and its versions from the store; the line of the command goes too. support/storage has remove.
  102. 564. The console prints a result whole, at any length. print formatted all its parts in a buffer of 256 bytes, and kept 192 bytes of a line for the log; the writer and copy dropped the rest with no sign. So a Text result longer than 256 bytes was cut in the terminal and in the log, and a line longer than 192 bytes in the log. The answer of storage.stores() passed 256 bytes with the store of the line history, and the machine test saw it cut in the middle of a store. Now a Text part goes to the terminal as it is (the terminal makes as many records as it needs), a number is formatted on its own, and the log line holds system.LOG_CALL_LIMIT (4096) bytes, the text of one Log call of the kernel, which writes it in as many log records as it needs; a longer line goes to the log in parts of 4096 bytes. system.log_text writes a text with no format and no buffer. The machine test prints a result of 324 bytes on one line.
  103. 565. A text is never cut in silence (docs/ARCHITECTURE.md, "A text is never cut in silence"): a text that does not fit is written whole in parts, cut with the cut mark, or refused with an error. The writer of support/sentence counts the bytes that it drops, and written ends its text with the mark; log_in writes a text whole, in lines of 512 bytes; the kernel ends a record of its own that passes 352 bytes with the mark; the Text answers of the storage service and of settings.get give the whole count; the console refuses a root ring name of more than 31 bytes.
  104. 566. One mark for the whole system: "... and N bytes more", after a whole UTF-8 character, in contract:cut_mark, which the kernel and the programs share. N counts each byte that the place does not hold, those that the mark takes back too. information.text_put used "..." with no count: it now uses the mark. A buffer smaller than the mark holds as much of it as fits.
  105. 567. A byte written after the mark is a missing byte: the writer writes the mark again with the new count at the next written; writer_reset starts a new text in the same buffer.
  106. 568. A partial check of the sources (text_cut_check): outside support/sentence and the tests, no source sets writer.count or reads writer.buffer[. It reads text, not types: a writer of another name escapes it.
  107. 569. The scenario: after the check of the log of the main machine, probe.log_long(1500) logs 1500 bytes in three lines of the log (9 records), and the tool waits for the end of the text; start with a name of 39 bytes is refused by the console with the count.
  108. 570. The idle timeout bounds a connection whose peer went silent (docs/QUIC.md, section 5; support/quic/connection.odin, recovery.odin). The three probe timeouts of the idle bound, and of the closing and draining states, are computed with no backoff (probe_timeout_base): a connection that receives nothing ends at most max(idle timeout, 3 × base probe timeout) after its last activity, whatever its probes. With the backoff, each unanswered probe doubled the bound and the bound moved past each next probe: a server kept the record of a vanished client for about 2 hours. Found by a machine step of the case "loss" that failed once under load, after a lost close. The peer of the tool sends the close of a client at the end of a case 3 times, outside the loss of the case; a new machine case "silent" checks the end by the idle timeout.
  109. 571. What moves the idle bound, a server always has one, and the machine proves it in its own time (review_quic_eighth.md, N43, W27, W28; docs/QUIC.md, sections 5 and 6). The last activity moves only for a packet that is recorded: a packet whose data had no place is processed but not recorded, and a replay of it no longer keeps a silent connection alive. server_init refuses an idle timeout of 0 (Empty, milliseconds, argument 5); a client may give 0, and its connection then has the idle timeout of the server. At an end by the idle timeout, quic_echo logs the silence in time of the machine, its bound, and a verdict ("held its bound": at least the idle timeout, at most the bound and a late wake of 5 s); the step "silent" reads the verdict, runs under KVM and under emulation alike, and is never skipped (the test of /dev/kvm is gone).
  110. 572. The compositor draws its names in B612 (docs/FONTS.md section 12.8, step 7): the buttons of the bar, the lines of the menus, the titles of the windows, the launcher; its values (clock, date, typed text, counts) stay in Paper Mono. The places of the bar come from the bar layout, measured in the font that draws it; bar.button_at, bar.line_at and bar.windows_line_at map a press to the entry that the eye sees. The compositor writes the layout in its log; the kernel tool measures the same layout and checks it. Two clicks of the scenario that used typed places of the buttons of the bar ask the layout now.
  111. 573. The boot display in two steps, as a rule (docs/DESIGN.md, section 5): the boot program draws its first screen in Spleen 8x16 (it holds no glyph cache); the root service draws it again with B612 for the titles and the names of the facts, and Paper Mono for the values, at no added byte.
  112. 574. A measure of the bar: one whole frame of the bar with the menu of the programs open, built and drawn, in each font, the least of 20, once in the log.

NEW-1. The site is the documentation of the system, generated by kerneltool site (docs/SITE.md; request of the owner, HANDOFF.md, "A technical site"; started on 2026-11-12, then made "doc like" by the owner the same day). build/site/ holds several pages: the home, which is the one page that shows the system; the guides, one for each document of docs/ (not HANDOFF.md and REVIEWS.md, working records); the reference tables (the glossary, the commands of the tool, the measured numbers, the licenses); the releases. No page holds a text that a person must remember to update: the text comes from docs/, README.md, CHANGELOG.md and the other files that each page names in its foot, with the commit, the release and the time of the build. One style sheet, no script, no request to another server; each link inside the site is relative, so the site works from a file, a folder of a host or a domain. A Markdown renderer in the tool knows the Markdown of docs/ and lists what it does not understand. Each number is a line of a log of the last machine tests, quoted whole. The command checks every page before it writes one: one h1, a title and a description that no other page has, an alt text, a width and a height for each picture, and each link and anchor inside the site. kerneltool check writes build/reproducible_build.txt for the site, and checks that each picture that README.md names exists. The address of the site is one setting, empty: the owner has not chosen a host or a domain. The site reads gui_toolkit/src/site for ideas only and uses none of its code. The status page, the reference tables of the code and the module pages: NEW-4 to NEW-7. Not done: the news, the search, the metadata for other sites, the feeds (docs/SITE.md, "Not done yet"). NEW-2. The licenses are tables, each cell from a file (owner, 2026-11-12: "be more table oriented ;)"). One row for each thing that has a license, and a second table for what builds and tests the system; each cell comes from LICENSE, the .license files of resources/fonts, flake.nix and flake.lock (small readers of the exact forms of those files, which fail with the line when the form changes), the pointer manifests, the license files of support/cryptography and of the portrait. A cell that no file records says "not recorded", and the command lists it: the license of Spleen and of each tool of the build. support/cryptography/LICENSE.odin.txt says the code was taken at the tag dev-2026-05, and the origin check compares with dev-2026-11a: the table shows both. NEW-3. The mark on a transparent background (owner, 2026-11-12). kerneltool logo also writes resources/logo/modus_transparent.png and modus_light_transparent.png, in its check, and the README shows them in a <picture> for the two color schemes. The site draws its own pictures of the mark from support/mark, transparent, at whole cell sizes (8 for the head of the home, 1 for the bar), and shows each at its own size; its picture for other sites keeps the dark background. NEW-4. The reference tables are read from the files that define each thing (owner, 2026-11-12: "une doc riche et auto générée"). The kernel operations and the status of a completion (contract/operation), the roles of the context (contract/operation, support/system/grants.odin), the error kinds and units (contract/errors), the grants (programs/root/main.odin, support/system/grants.odin), the fixed ranges (contract/fixed_addresses), the built-in commands of the console (programs/console). Small readers of the text of each file (tools/kerneltool/site_source_readers.odin) take the names, the numbers and the comments, because the comments of a contract are its documentation and the compiler drops them; each reader knows one form and stops the command with the file and the line when the form changes. The host tests compare each enumeration that a reader finds with the enumeration that the compiler knows: the same names, values and order. The parameters and the result of a kernel operation exist only as the words of its comment, "(parameters) -> result": the table shows those words. A comment line inside an enumeration continues the comment of the member above it: a heading comment before a member (as "The word wait" before Word_Wait) shows under the member before it. The roles 13 and 15 of the context have no constant in contract/operation, only a comment: the table does not show them. The address of a fixed range comes from fixed_addresses.range_of, not from a copy of the constants. NEW-5. A module page for each module of module_builds (tools/kerneltool/site_module_pages.odin). The page gives the documentation of the module (the comment before package, the text that the build writes into the module), its exports with their signatures as the console prints them (write(name: Text, data: Text) -> Integer), its imports and the modules that import it, its size in the boot image of the last build, and for a program (a source in programs/) its grants and its budget from grants_of and things_of of the root service. The signatures come from describe_package, the reader that the build uses: no module needs to be built for its page. NEW-6. The status page (tools/kerneltool/site_status.odin; owner, 2026-11-12, of a part "What runs today": "watch out cause it'll be a nightmare to udate the description"). Each property with its status, the column of the summary of PROPERTIES.md; the evidence items and the gaps of this file that name it ("property <n>" or "P<n>", one whole word); then each gap with its group, and the next steps. The page has no sentence of its own about the state: each cell is a part of the two documents. The ids of the evidence items are those of the guide, because the page renders this file again with the same renderer. NEW-7. The tiles of the boot image on the home, and links from the guides to the reference. The home shows one tile for each module of the boot image of the last build, as the boot display draws it: its width from its size, its color from the first byte of its hash. The rule of the color, hue_color, moves from support/bootscreen to support/graphics, so that the boot display and the site use one rule. A code span of a guide that is the whole name of a kernel operation, an error kind or a module, or a call of a built-in command of the console (load("math")), links to its row; a name that two tables have (Bad_Address is a status and an error kind) links nowhere. NEW-8. The news: a second record beside the changelog (tools/kerneltool/news.odin, site_news.odin, news/README.md; owner, 2026-11-12: "we could have a \"parallel changelog\" (changelog is very technical/list-like) which would be like a \"status report\" or like \"News\""). A news entry is one file of news/, named by its date and its subject, never moved or merged, as a fragment of the changelog is one file: a head of six keys (title, date, release, picture, kind, guide) and a text of at most 1200 bytes. Four kinds, each the colored mark of a card: Shown, Measured, Decided, Fixed. kerneltool news new writes an entry with no title and no text, so that the check fails until a person writes them; kerneltool news check is part of kerneltool check. The site shows the entries on news.html, the three newest on the home, a link from each release to its entries, and the feed news.xml in Atom, whose ids are the addresses of the files in the repository, so that they do not change when the site gets an address. The home has no prose of its own about what runs: the newest news, the tiles and the numbers. The first eleven entries are written from the dates of CHANGELOG.md, the evidence items of this file and the steps of REVIEWS.md. A step adds an entry only when a person can see or use something new. NEW-9. The search of the site: one index, one small script, the match of the console (tools/kerneltool/site_search.odin; owner, 2026-11-12: "une doc riche et auto générée, avec recherche, modules, etc."). kerneltool site writes search.json, an entry for each page and for each row or section that has an id (modules, exports, operations, error kinds, commands of the console, terms, sections of the guides, properties, news, releases, gaps, rows of the licenses), checked against the written pages: each page has an entry, each entry names an id of its page. Bounds: 4096 entries (the names that one ranking of the console reads), 1 MiB, a title of 128 bytes, a text of 120 bytes. search.js is plain JavaScript written by the tool, loaded with defer; it fetches the index on the first key and ranks it with the fuzzy match and the order of support/line_editor/fuzzy_match.odin, ported rule for rule; the tie order is the kind. The tool writes the constants of the match into the script, and one table of queries, names, scores and orders is run by a host test over the matcher of Odin and by the script with ?selftest=1: a change of the rule fails the host test, then the self test of the script. The look is the completion list of the console: a box at the right of the bar, at most 8 rows, the matched characters in the accent, the kind at the right, Up, Down, Enter, Escape, / to focus, the query in the address as ?q=; the roles of a combobox and a listbox. With no script, the box stays hidden and every page is reachable by its links. The check of the pages now refuses inline code and inline style. Refused: a library of search, a build step of JavaScript, an index made in the browser from the pages. Not done: the self test of the script runs in a browser only, since no program of the repository runs JavaScript; a page opened from a file cannot fetch the index. NEW-10. What other sites read: written and checked only with a site address (tools/kerneltool/site_metadata.odin; owner, 2026-11-12: "we'll want a sitemap, json-ld, good seo (graph) etc"). A canonical address, Open Graph and the @ids of JSON-LD are absolute, and a relative one is wrong for the program that receives it: so kerneltool site writes them, with sitemap.xml and robots.txt, only with a site address (--address https://<host>/<path>, a path works), and says which parts it did not write with none. Each page has one page graph, built from the data that the page shows: the site with its search, the system as a SoftwareApplication (an operating system on x86-64, tested under QEMU), its source code, the picture for other sites, the page, its breadcrumbs, and a TechArticle, a DefinedTermSet, an ItemList or a Blog with a BlogPosting for each news entry. The JSON is escaped for a script element, and the check of inline code allows this one script of data. A date is written only when it is true: the date of a news entry, of a release, and the lastmod of the news and of the releases in the sitemap; a guide has no date. The checks: one canonical address for each page, its own and unique; the Open Graph picture exists with its stated size; each graph parses and each id that it names is a node or a page; the sitemap lists exactly the written pages. Also written in both cases: releases.xml, the feed of the releases with stable ids; site.webmanifest; llms.txt; rel=prev and rel=next between guides; each picture of the repository written again compressed, with the same pixels (site_png_compress). Not done: author and publisher (the owner decides whether the graph names a person); a validation against schema.org or a search engine, since the work runs with no network. NEW-11. The site menu, the author, and the publication by a workflow (owner, 2026-11-12: "il manque un menu burger sur la version mobile du site", "pour l'auteur du site évidemment tu peux mettre", "pour la publication du site je préfère un workflow GitHub évidemment", and the news cards of the home that do not align: "ça fait dommage"). (1) The sections and the search of the bar are in one details element: one list of links at every width, so a screen reader reads each section once. Under 720 pixels its summary is a square button with three lines drawn by the style sheet and the hidden word "Menu"; on a wide screen the menu is open and the button hidden. A closed details cannot be opened by a style sheet in every browser: a browser with ::details-content shows its content with no script, and search.js, already loaded on each page, opens it on a wide screen; with no script and no ::details-content, the button shows and works. The menu is closed in the file, so that a narrow page does not move after its first paint. Refused: two copies of the links, a checkbox as a button, an inline script. (2) The page graph names a Person from the line Copyright of LICENSE (its holder, nothing else) with the address of the account that owns the repository: the author of the program, of its source code, of each guide and each news entry, and the publisher of the site; <meta name="author"> on each page and <author> in the two feeds. (3) .github/workflows/site.yml builds the boot image and the site with the tool, and deploys build/site as an artifact of GitHub Pages; the domain is a setting of the repository, with no CNAME file; the headers come from the proxy of Cloudflare, since GitHub Pages sends none of its own. A clean checkout has no log of a machine test: the numbers say "Not measured in this build.", and the owner decides whether the workflow runs the tests. (4) The news cards of the home share the rows of their grid (subgrid): the title, the line, the picture and the text of each card of a row start at one height; with no subgrid, a title keeps the room of two lines.

Gaps

  • A fault at the return to a program is a fault of the kernel (Review 10): the kernel refuses the one known cause (a thread whose entry or stack is not an address of a program), but any other state of a thread that the processor refuses at iretq would stop the machine. To do: treat a fault at that instruction as a fault of the program.

Against the properties and the module contract:

  • The SSE2 forms (docs/SIMD.md) are measured on the host only, at a load of 10: their speed on the machine is not measured yet. The horizontal gradient stays scalar. No AVX2 form: the kernel does not save the extended state, and the AVX2 forms that were written lost to SSE2 on the host.
  • Property 19 is not complete: the description has no floats, no text of a variable size in a record, no record in a record, and no handle in a result.
  • Tables as parameters (417 to 428): the console cannot call an export with an Append table or with Bytes that change (the language has no second result), and kerneltool call gives tables to read only. A record of a table has no Handle. The direct call checks nothing: a caller in the same address space that gives a count larger than its memory makes the export read past it, as with any address; only the isolated call checks. In a lent memory object the callee can write each byte of the object while the call runs, not only the table: the granularity is the object; only a call host takes this form, a service refuses it. The receiver checks overlap between parameters, not between the records of one parameter that name bytes of another (two packets of seal on the same bytes). The call region is shared during the call: an export that checks a value of a record and uses it later copies it first (as for a Text, choice 186). The console starts call hosts with its own code, not with system.call_host_start. The kernel gives each mapping that chooses no address a new range and never reuses one: a program that maps memory at each call with no address grows its page tables until its budget refuses (421 works around it in the receiver; the kernel is not changed; the fault is given to the agent of work/scheduler). The measures of 427 ran at a load of the host of 32 to 48.
  • interface_module refuses an element larger than its element frame: 64 draw records or 4096 bytes of text in one element call.
  • The interface package is a module for one program only (interface_through_module); the elements that take input (a button, a list, a text input) are not exports: they need the interface state of the program as a table of one row with the direction Change.
  • Information functions: the catalog is the boot image and the replacement table: the module store does not exist. The error record of a malformed module gives the kind of the problem and its byte; problem(name) gives the sentence. manual reads the module records with the machine and the right Inspect, which also shows the state of each program: no grant gives the module records only. A row holds a hash as 64 hexadecimal digits, 32 bytes more than the hash; a signature longer than 112 bytes ends with "...". A function that answers about all the modules checks each file each time, with no cache: the measure includes it. The functions trust the hash of the entry of the boot image: they do not compute the hash of each file. The console keeps 256 KiB of tables for one line and 32 KiB in a variable, and a record that join makes has 256 bytes at most: columns chooses fewer fields. where compares for equality only. The module records come from the kernel, so running() shows a module that a program maps by itself only if its loader wrote a record (gap of choice 165).
  • Imports and replacements: the replacement table binds the loaders of the system: the root service, the console, the call host. A program that maps code by itself can ignore it: the kernel does not check what a program does with its own memory. A module record is what the loader asserts: a program writes and removes the records of its own program, and the kernel does not compare them with the memory. A change of the table does not reach a program that runs: the root service does not start a program again. The loader finds a module only in the boot image, and the person names a module of the table by its name there: the module store does not exist. The check cannot prove that the module of a replacement behaves as the module that it replaces, and no principal signs the table (P9). A module has 8 imports at most, a program 16 modules at most, the table 32 records. A load error holds the first failure only, and one replacement of the path; it does not say which field of two Records differs (see MODULE_CONTRACT.md, "The error of a load"). The root service writes the error of a start on the log only: the program that asked the start receives a status.
  • A direct call does not limit a Handle to the rights of its description: the module is in the program of the caller and has each handle of that program.
  • Revocation: a grant has no end time. The root service cannot revoke one ring (it closes its handle after the start): to end a connection it uses Disconnect or stops a program. The person sees the grants of a program in the console only: no window shows the grants of all the programs. A grant that the root service did not make (a handle that one program gave to another) is in the kernel table only: no command shows it.
  • A caller can give handles through a call ring and never send the call: the handles fill the 128 places of the table of the receiver until the caller revokes them or stops. A service with several callers needs a limit for each caller.
  • program.has_ports stays true after the last Ports handle of a program closed: the change of program on a processor then reads the handle table for nothing.
  • The console makes a handle only with buffer. It cannot make a handle with a part of the rights of another handle.
  • A service: the root service has a list of the services that a program can call only for a program with the grant Connect (callable_of, today remote); the console connects to each service. A caller finds the file of a service only in the boot image. A call that never returns stops the one thread of a service such as paint: the person stops the call in the console (the console closes the connection) and closes the window of the service.
  • A terminal shows one program, and a program has one terminal. A program that has a terminal has no surface. The terminal gives keys only: no pointer, no paste, no size of the view. The person cannot select or copy text. The terminal shows bytes as characters of one cell: no Unicode beyond the font, no wide character. The terminal of a stopped program stays until the person closes its window: no command of the console closes it.
  • Only terminal has two pictures: the other programs can still show a part of a new picture with a part of the old one.
  • The kernel ring of a thread has 32 records. workers still takes its tiles from a queue with a lock, not from a shared queue.
  • A thread that stops in the middle of a write or a read of a shared queue stops the queue: no thread repairs the record of a thread that is not there.
  • The milliseconds of the time page change at a tick, each millisecond; a finer time is system.nanoseconds(), from the cycle counter (choice 250). A deadline of a thread still has the resolution of the tick. No check compares the cycle counters of two processors. The time page has no date: the kernel does not read the clock of the machine.
  • Limits: the root service gives each program the processor time of all its threads: no program of the desktop is limited below what it can use, and no command changes a limit while a program runs. A thread that another processor stops loses the count of its last part of a time slice (less than 1 ms). The scheduler passes over a thread with no time left each time it looks: with many such threads it reads them for nothing. The log limit is for each second on the tick: 100 records at the change of second can pass as two times the limit.
  • A program can write, change and remove its own module records (Program_Module with program 0): the records that its starter wrote are not protected from it, and a report does not say who wrote a record. Found by the coordinator at the merge of step 34. Remove: the kernel marks each record with its writer (the program itself, or a holder of its handle), and a program cannot change a record of its starter.
  • Error records (choices 227 to 241): the kernel finds the record of an operation after it failed, from the operation table, not from the check that refused. An operation whose checks change without a change of the table gives a wrong argument, or Refused; the program errors tests 10 kernel cases, not each check of each operation. These refusals give Refused with the status: the ranges of Device_Create, Memory_Physical on memory that is not contiguous, No_Memory, and a Bad_State of Handle_Give through memory. A record holds the first argument that is wrong; a table of error details gives each one, but only monitor.counts_named and the loader (Find and Read) declare it, and a kernel operation has none (the design is in MODULE_CONTRACT.md, section 8, "The table of error details": a bit of the kind of a command, not built, because it costs one instruction for each command). A call host gives no details of its load. A direct call of an export with a table of error details sets one more register, also when the caller gives none. The record of an isolated call comes from another program: the caller checks the kind and cannot prove the values. Only the storage service, the information exports of manual, the exports of monitor and math.square_root give an error record; the other exports give a negative number or nothing. The stack trace has 16 frames at most, and names a function only on the host, with the linked file: the system has no debug object (decision 5 of MODULE_CONTRACT.md). The kernel does not log the frames: the holder of the program handle reads them.
  • The menu of the windows has no keys: the person needs the pointer. The launcher starts programs only: it does not bring a window to the front. Two windows with the same title have two equal lines. The scenario still has clicks and drags at fixed places inside windows that it placed itself.
  • Memory_Unmap does not free a page table that became empty: the budget of the program pays for it until the program stops.
  • One source gives one module: proved for one compiler on one host (the open fault "one build did not give one hash one time" is closed by 400 to 402). kerneltool repeat all 100 with the patched compiler, under a host load of 9.5 to 62 (median 26, 124 samples, 2026-11-11 13:26 to 15:29): for each of the 61 modules, 100 builds gave 1 module, 1 LLVM IR file and 1 object; 100 builds of the boot program gave 1 program and 1 object. Not proved: another version of the Odin compiler or of LLVM (the patch is for tag dev-2026-05; a new version needs the patch again, and repeat all again); another host or processor; the tree after the rebase on step 54 was checked by the full check (two builds), not by 100 builds. -thread-count:1 makes each build of a module single-threaded. The parallelism is one level up: one compiler process for each module (HANDOFF item 43). The report of the correction, with the compile time and what a deterministic order inside the compiler needs, is docs/REPRODUCIBLE_BUILD.md.
  • The kernel tool keeps the frame pointers of each module that it builds (choice 237). A module that another tool built can omit them: the loader does not check, and the stack trace of such a module stops early. An import call goes through a stub that the linker adds.
  • The test "a C program calls an Odin module" does not exist.
  • The manual shows the modules of the boot image only: the module store does not exist. The documentation has one language. A module in C needs a description file for its documentation: the tool reads Odin sources only.
  • The console has no command that ends the connection to a service.
  • A thread has a home queue, but no affinity: the balance can move it at each choice of a thread.
  • Scheduling strategies (choices 269 to 283): one strategy for the whole machine; no strategy that a program supplies (no verifier, no watchdog). A weight is for a thread, not for a budget: a program with more threads has more share. The balance counts threads, not weights: two threads of one weight in two queues with different weights get different shares. Round robin has no wake preemption (it is the reference). A wake preemption is decided under the run lock and acted on after it: if the home runs another thread when the interrupt comes, that thread loses its turn. A thread of another program preempts a processor at most once in each millisecond; a program can still make the threads of other programs pay that one preemption in each millisecond on each processor (about 2 us in the kernel each, measured), and a program whose threads wake each other preempts its own threads with no limit. The tick ends a turn under the fair strategy and EEVDF when another thread is in the queue, also a thread whose budget has no time in this period: then the pick gives the same thread again, a pass through schedule for nothing. A count of the threads that can run is not kept: a budget becomes spent with no event on the queue, so the count would need a walk of the queue at each tick, which the pick already does. The hint look_first holds one queue: a second wake before the pick replaces it. A change of strategy resets the virtual time and the lag of each thread: a program with the right Schedule that changes often moves each thread by up to one bound (3 ms, or two requests) at each change. A request of 1 us gives a new deadline at each tick: more switches, paid by the budget of the thread. A pick reads the whole queue: no measure with long queues. The delay of a wake is measured at a deadline, on the tick of 1 ms. The numbers are from one run; the test asserts no speed. Tested under QEMU with 4 processors only.
  • The virtual memory of a program (choices 432 to 442): an object whose parts have their own protection is not shared (the marks are of the object). W1 (a fault through an old translation of a large page) is corrected by reading only, with no test: a test needs a fault inside the window of a shoot-down, which a program cannot make on purpose. F1' of the second read is corrected by reading only. A whole mapping of 4 KiB pages counts its present pages against the limit of one call (choice 444); a part counts each of its pages, and a part needs an object that only the program names: a part of more than 65 536 pages is several calls. The walks of a revocation, an unmap and the end of an Uncommitted object stop at the last frame or the last present entry; an object whose last page has a frame is still one pass over its slots (262 144 for 1 GiB, 2 MiB read in order, with interrupts off, under the frame lock for the release): the time is logged by address_space, not measured on a quiet host. The cases of the third read are written and not run (the coordinator builds and tests). No fault ring (a fault stops the program, as before). Decommit and the protection of a part need an object named only by the program, with one mapping. A protection change of a part of a large page is refused, not split. The map of a range of 1 GiB makes all its page tables at once (2 MiB, paid by the budget of the program), and Memory_Unmap still keeps them. The size of a large page is 2 MiB only (no 1 GiB page). The benefit of large pages was not shown: the runs were on a loaded host under KVM. The speeds of this step were all measured with a load of the host above 8.
  • Frame caches (choices 348 to 354): the global exhaustion with frames in the stacks was not produced (the drain is tested by the hook of the test build, not by a real shortage); the zero at the put was not measured; a processor that only frees fills its stack and gives back by batches, a processor that only takes refills by batches, an idle processor keeps up to 256 free frames until a drain: each case is argued and seen in the counts of locks (batches taken and given differ by processor), not measured alone. Page tables and list pages still take their frames from the frame table. The rates of pages on first use were measured on a loaded host and do not show the change; the counts of the frame lock do.
  • The program lock serializes the kernel entries of the threads of one program, also the word waits and wakes: over one machine test, program locks waited 1.06 million times of 18.1 million takes (memory()), mostly in programs whose threads wake each other.
  • The control lock is taken by Program_State, Thread_Stop, the reports of other programs and each lent handle: a program that polls the state of another program often (monitor, a terminal) takes it each time. It waited 67 times in one machine test.
  • The event of an interrupt goes to the program that held the interrupt handle when the interrupt came, read with no lock: a handle that closes at that moment can give one more event to its old owner.
  • The order check costs about 10 % of a cheap kernel entry. It has no separate build without it.
  • Shared code: each program that starts programs has its own table of prepared modules: two such programs that start the same module prepare it two times. A module that a program loads in itself (load in the console, the module of a call host) is not shared. A prepared module stays after its last program until the next start by the same program. The loader computes the hash of the module file at each start.
  • Pages on first use: no page of zeros is shared, so the first read of a page takes a frame, as a write does. A program cannot give back the frames of a part of a memory object that lives. A memory object that is not contiguous has at most 1 GiB.
  • The revocation of a mapping walks the frame list of its memory object for each word that a thread of the program waits on. Program_Report walks the page tables of the program to count its present pages.
  • The 16 pages of the stack of the boot program stay in use after the kernel leaves that stack.
  • With a processor that has no SMEP or SMAP, the kernel runs without these two guards. pages and the log say so.
  • A reader with the grant reads the records of all the programs and all the channels: a grant for one channel does not exist.
  • The boot display does not show the records of the log. A long text is cut at 80 characters in the log window.
  • The boot display needs 976 x 700 pixels. On a smaller screen it is cut.
  • A thread that stops with a lock held keeps the lock: the other threads wait with no end.
  • The machine test failed one time in four runs of step 24 at the step that moves the window of paint ("the window paint is not at 516,408"), and passed at the next run with no change of that code. Step 27 found a cause that explains it (choice 127: the tool read a part of a line of the log) and removed it. The log of the failed run was not kept, so the cause is not proven for that run: if the failure comes again, the cause is another one.
  • No QUIC, no document service, no principals, no TPM, no Secure Boot.
  • The developer tool inspect (choices 316 to 324): it reads no byte of another program (no such right yet), and the stop report through Inspect hides the frames out of a module. A program that stopped has no mapping: the kernel ended them, so the map of a stopped program is empty and only its stop report and its modules stay. The fields of the scheduler of a thread are read with no run lock. The tool shows the first 11 mappings in its table and the first threads that fit; the exports give all. It names a function only when it is an export ("near"): the system has no debug object. It does not show the handles of a program, its budget in a picture, or the counters of the processor (item 94 (4) of HANDOFF.md).
  • Calls from the host (choices 252 to 262): UDP has no authentication. A machine on the link of the machine that sends a datagram from 10.0.2.2 port 7100 makes a call with the grant of the remote service, which acts for the person, and reads the answer if it sees the frames. The grant of one remote filters the address; it does not prove it. QUIC, a later step, gives authentication; this step adds no password, because a secret in a datagram with no encryption protects nothing. The person can revoke the grant person, or stop remote.
  • Calls from the host: a call request is one datagram, so the bytes of the arguments are 1368 at most. The host gives Integers and Texts only: no Record argument (no text form yet) and no Handle (a handle of the host names nothing in the machine). The remote service runs one call at a time, keeps the last answer only, and has 8 connections. The peer gives each answer to the program that sent the last call request: two programs of the host that call at the same time get each other's datagrams, and each one drops the parts of another number. The tool reads build/modules: a machine of another build refuses each call by its hash. The window of remote names an export by its number. Most checks of the test are still .Wait on the serial port; 11 are steps Record. The presses before remote runs, and on the machines with no remote, are not confirmed. The compositor and the log serve a call between two frames or two reads of the log: a call waits for the end of a frame.
  • Network: the address is static in the network service (10.0.2.15/24, the gateway 10.0.2.2): no DHCP. A grant names an address, not a name: no DNS. No IPv6, no fragment (a fragment that arrives is refused), no IPv4 option in a frame that the network service writes, no ICMP message but echo (no "port unreachable").
  • Network: the network service writes the headers of UDP and IPv4 for each program (choice 194), and checks the ports and the checksum of each TCP segment (299): TCP is in the program, QUIC is a plan. The network service is on the data path, with one copy, because the device has one pair of queues.
  • Network: one endpoint for each program, one remote for each grant, 8 endpoints, 128 Receive places in flight for each endpoint. A listen grant receives from any remote and answers only the admitted ones (297); it names no address or range of addresses that it accepts. The grant comes from the name of the program in code (endpoint_of); the person cannot give a new grant while a program runs, only revoke it.
  • Network: the table of hardware addresses (16 places) learns only the answer to a request of the network service (choice 206), but a machine of the link can still answer such a request before the gateway: no ARP frame is signed. An entry stays until the table is full. One ARP request, no retry, 1000 ms for an answer.
  • Network: a datagram that arrives when no Receive waits is lost (Statistics counts it). The frame service uses one message interrupt for its two queues and does not ask the device for fewer interrupts. The network device reaches all the memory of the machine, as a disk (no IOMMU).
  • Network: the peer of the test checks the frames with a second implementation of the same rules, written in the same step; the known answers come from RFC 1071 and one IPv4 header. A handle that the network service received for an endpoint that the root service then could not announce (a full control ring) stays in its table.
  • TCP (295 to 309): no window scaling (one connection has 65 535 bytes in flight at most), no SACK, no timestamps, no TCP Fast Open, no ECN, no Nagle rule, no keepalive, no SYN cookies (a full table refuses a SYN with a RST), no path MTU discovery, no urgent data, no IPv6. The least timeout is 200 ms and TIME-WAIT is 10 s, both shorter than the RFCs, for one link. FIN-WAIT-2 ends after 20 s (fetch aborts after 2 s). A segment to a port of no endpoint, and a segment of a remote that sent no SYN to a listen port, are dropped and counted: the network service sends no RST for them. server reads one request at a time: a second request in the same segment is lost (no pipelining).
  • TLS (466): no HelloRetryRequest (a server that asks for another group fails the handshake), no session resumption, no client certificate in this layer, no X.509. The transport (468) checks the values of the transport parameters.
  • QUIC on the network (506 to 517): no stateless reset; a flood that outruns one pass of quic_echo fills its places for datagrams and the network service drops real datagrams too; a flood above a shared budget (24 MB/s of Initials with no token) stops new clients while it lasts (a budget for each source prefix is designed, not built); a host with real addresses holds 2 records in the handshake of each of its addresses; a NAT rebinding ends a connection; the key of quic_echo changes at each start; remote is not over QUIC yet, and the machine does not authenticate its clients (no principals).
  • QUIC transport (468): a fixed congestion window, one stream, no key update, no Version Negotiation, no Retry, no stateless reset; a NEW_CONNECTION_ID with Retire Prior To above 0 closes the connection; packets of a level whose keys are not ready are dropped; a handshake ends after 10 s.
  • Cryptography (460 to 462): no hardware AES or SHA (AES-128-GCM is 4.6 times slower than ChaCha20-Poly1305 in the portable code); no batch export until tables as parameters are merged; the arithmetic of the origin has no independent review (its README says so), only the RFC vectors.
  • Randomness (403 to 405): no entropy device is driven (virtio-rng-pci of QEMU is the next layer, through random.reseed of the root service); no service gives fresh bytes to a running program; the TPM (property 17) is not a source; the jitter of the cycle counter is not measured, so it is counted as 0 bits. The output of rdseed and rdrand is not tested for quality: a wrong processor gives a wrong seed with no sign.
  • TCP: a host that sends SYNs from 64 ports in less than 60 s fills the admitted remotes of a listen grant for 60 s (334); its records end in 15 s and the program releases them, so the table frees earlier when the program works. A host that keeps 24 connections open with progress keeps half the records of server. Many hosts with no SYN cookies fill the table: a SYN then gets a RST.
  • Same bytes (choice 359, the open fault): with the corrections of 372 to 378, server meets the second cause of choice 359 in its own path: kerneltool repeat server gave 0, 4, 0 and 5 builds of 15 that differ across the last commits, 0 of 11 for server of master in the same path, and 0 of 11 for the same commit in another path (the module holds the path of its source). The only difference in the IR of each pair: the place of the global graphics::font among the globals, an order of the work of the compiler, not a construct of the code. A differing pair is kept: /tmp/kernel_same_bytes_server/server_ir_first.ll and server_ir_2.ll. The correction at the root is the work of work/samebytes.
  • HTTP (372 to 378): storage.call has no deadline: a storage service that hangs during a put stops the loop of server. A deadline alone is not right: the call region is shared, and a late answer of the service would write over the next call; it needs a busy state of the client (or put as a state machine of the loop). A Release that finds releases_waiting full is counted and lost: the network service frees its place after 120 s with no segment.
  • HTTP (341 to 346): no TLS: the network is not authenticated and not secret until the work of QUIC and TLS 1.3 (docs/NETWORK.md, "QUIC"); so PUT and DELETE stay off. No chunked request body (501), no Expect: 100-continue (the server reads the body without the interim answer), no ranges, no conditional requests, no compression, no Date (no clock of the day), no virtual hosts (Host is required, not used), no URL decoding of % in a target. A line end must be CRLF: a bare LF ends no head (the client gets 431 or 408). A page has 60 KiB at most, 16 pages. The request log writes 100 records in one second at most. The answers of one connection are one at a time: a pipelined request waits for the answer before it.
  • TCP: the peer tests support/tcp against itself; the test against another implementation is libslirp only, which relays to the TCP of the host. No test runs the TCP of the system against the TCP of the host kernel directly, with no relay.
  • A device reaches all the memory of the machine: the machine has no IOMMU in use, so a block service with a fault can make its device write any page. The block service checks that a request names memory of the client; the device does not check it. Property 5 names the IOMMU as a requirement on real hardware.
  • A block service serves one client. The storage service is the one client of the data disk; a second client needs its own disk, or a multiplexer of block rings.
  • The places of the disks on the bus are fixed in support/pci: the boot record does not give the place of the boot volume.
  • The block service uses one queue of the device and one message interrupt. It does not ask the device to send fewer interrupts while many requests are in flight.
  • The crash test of the persistence machines ends QEMU at a time that the tool chooses from the log; the test of a loss of power (choices 382 to 384) ends it at the write of a root. The cut of a root block is made by the tool, not by a stop of the machine.
  • A revoked store: notes sees -1 at its next change and keeps nothing more; its window says it. The root service gives a store by the name of the program only.
  • A store has at most 48 objects, and an object at most 32 blocks (128 KiB); the call region of a connection (64 KiB) is the true limit of one write. The disk has at most 60 stores. The tables of all stores stay in the memory of the storage service.
  • The storage service does not encrypt the disk, and the TPM does not seal a key (P17).
  • A store is named by the name of the program, as the root service names it: two programs with one name have one store. Principals (P21) are not in the name yet.
  • The storage service serves one call at a time; one call waits for each commit (two flushes).
  • The loss of power: one seeded loss for each run, at the write of a root; the images of the search are built from the writes after the last flush, which is one write at that moment (the blocks of the commit were flushed before). A loss in the middle of a large write of many blocks, and a disk that reorders the writes before a flush, give the same images (any subset): the search covers them, with no machine that makes them. The checks of an image run on the host; one image starts a machine.
  • The storage service does not check, at its start, that a block has one owner or that the count of blocks of a store agrees with its table. The check of each block finds a change from outside; it does not find a fault of the service itself.
  • A store keeps its entry and its table block after its last object is removed. The limit of a store is written again at each change, from the grant.
  • A service must copy each Text argument that it checks, as the storage service does for a name: the call region stays writable by the caller during the call. The contract does not do it for the service.
  • A damaged store (a wrong table) refuses each call: the person has no way to drop it and start a new one.
  • When a block service stops while a client of it keeps its data region, the device can still finish a request in that region before the root service turns off the bus master.
  • The pointer (360 to 370): a program has one shape for its whole surface, and changes it with a request; it receives the pointer only when it has the focus, so a window that is not in front keeps the last shape that it asked. The compositor draws one size: the pictures of 64 and 48 wait for a screen with twice the pixels and a scale of the compositor. Windows have no resize, so the compositor never chooses a resize shape. The order of the themes is fixed in the root service until the settings service exists. The tool reads Xcursor files only (no hyprcursor, no .cur or .ani) and scales no pixel. pointers shows the first frame of an animation. The root service computes the hash of the whole theme at each start (1.4 to 1.8 ms). The theme of the owner has smooth edges against section 2 of DESIGN.md: an exception that the document states. The measures of speed of this step come from runs with a load of about 30 on the host.

Performance:

  • The desktop: the places of new windows are not on the grid of 8 (264). A title that is longer than its title bar is cut. The launcher lists only the programs of bar.names, not the modules of the boot image or of a store. The launcher is drawn by the compositor: it is not a program, and it has no picture of its own.
  • Programs that still have their own palette: docs/DESIGN.md, "State".
  • runtime/memory.asm has one form for each size range, chosen on one processor (AMD Ryzen 7 5800X, ERMS and FSRM): another processor can have other crossing points. The thresholds are constants of the file, not measured at the start.
  • The new places of windows are not on the grid of 8: 13 of the 19 fixed points that the main scenario presses depend on these places. With compositor.windows() (choice 289 and after) the tool can compute each point from the place of its window; then the places can move to the grid.
  • The interface package is a support package compiled in each program; one program uses it as the module interface_module (427). Three users use it compiled in; the other windows still draw by hand (the next steps of docs/INTERFACE.md). The damage by difference matches records by index: a record added in the middle damages each record after it. A surface with two pictures is drawn whole.
  • The vector font: one glyph from the glyph cache costs 3.2 times a glyph of Spleen at scale 1 over a flat color on the host, 5.3 to 5.6 times over a gradient across or a picture (docs/FONTS.md, section 5); the machine measures were taken with a host load above 8. No grid fit of each stem, no proportional font, no character outside ASCII 32 to 126. The boot display uses the default settings until the boot pass of settings. The order of the stripes of the screen is not detected.
  • The compositor keeps at most 8 damage rectangles and draws only these for each frame. With plasma open it uses about 7 % of the processor under QEMU (60 % before, with a full redraw). A program gives the part of its surface that changed with display.present_rectangle; only the users of the interface package do.
  • The extended state is saved and restored at each switch of threads, also when a processor takes again the thread that it left just now (a restore that a field "the owner of the registers" could skip; ARCHITECTURE.md, "The extended state of the processor", section 3). The kernel form of the copies of memory uses rep movsb from 65 bytes: its speed against the loop of xmm registers is not measured.
  • The extended state: AMX is not enabled (an instruction of AMX stops the program). A processor that cannot save what the first processor saves stays stopped, and the machine runs on the others. A processor with xsave and no xsaveopt gets the fxsave path, so no AVX. The area of each thread is 2688 bytes in the thread table of the kernel also on a machine with no AVX-512 (544 KiB more than with 512 bytes). The host of the tests has no AVX-512: the zmm, opmask and AVX-512 setup paths (the 2688-byte save, the layout check of the other processors, xsetbv with bits 5 to 7) did not run in a test machine; the tool tests them on a host with AVX-512 and KVM. Without AVX-512 BW the test holds the opmask registers with kmovw: 16 bits of each of the 64. The start state check of a thread does not check the low 16 bytes of xmm0 to xmm15: the compiled code of the thread used them first. The measures of this step were not run by its author.
  • display.open repeats Handle_Give until the compositor maps the ring.

Input:

  • The keyboard layout is US only.
  • The console line (docs/CONSOLE.md): a stop that the console does not see (a close of its window, a fault, a loss of power) loses the line that runs then; no machine case puts a loss of power inside a commit of the line history; the history store keeps the versions of the history until the next commits thin them; the completion does not complete the second argument of a call, the fields of a record variable or the common start of several names; the fuzzy match takes a bounded window, not the best one; the search shows the best 64 lines; a list in quotes of connect names the programs that run, not only the services, because the root service gives no list of its services.
  • The error records of the other calls: the frame ring has no machine case that refuses (its one client, the network service, sends right requests); the block ring has one (disk). errors has no datagram case: it has no endpoint; net and fetch show the datagram refusals. A record of an isolated call, a ring completion included, comes from another program: the caller checks its kind and its unit, and cannot prove its values.
  • Versions: a remove ends an object and all its versions (an explicit act of the program); the system has no bin for a removed object. A program cannot group several commits into one step that the person undoes (property 9, open). The retention rule is fixed: no setting of the person sets it. The time of a version is the time that a machine ran since the format, until the date of part 2. The refusal of a change counts a block that two clones share two times: it can come early. A table holds 39 objects now (it held 48). The engine itself has no host test: a commit needs the block service. Its corrections (drop of damaged versions, compaction in a commit, refusals of a count) are tested on the host only through the pure procedures that it calls (storage_mount) and by a model of its rule of the index; no machine test makes a damaged index, a full index, or 21590 restores. A list that goes wrong while the service runs keeps its blocks in use until the next start. The service never compares its counts in memory with a recount while it runs. After the root service lowers the limit of a store, each change that does not shrink the store enough is refused; a remove still works.
  • The date: from the real-time clock, which a person with the machine can set to any date; read each 10 s, so the date moves by the drift of the time of the system in 10 s; no network time, no time zone (UTC). A program that the root service did not start (a node of rings, a twin of frames) has no date page. The dates are 2020 to 2099 (two digits, no century register: the ACPI table FADT is not read). The grant of the two ports of the clock lets date write any byte of the memory of the clock: the processor cannot give a narrower grant without a change of the kernel.

Next steps

The order, as told to the owner on 2026-11-10:

  1. 1. Revocation of a grant: done (step 25), Review 5, processor time and log records in budgets: done (step 26). A window comes to the front by its name, for the person and for the test: done (step 27).
  2. 2. A program loads a module with imports; replacement of an import (MODULE_CONTRACT.md, "Replacement of an import"): done (steps 33 to 35, choices 160 to 171).
  3. 3. Paging, second part: pages on first use, and read-only code that programs share: done (steps 28 and 29, choices 128 to 141).
  4. 4. The kernel lock split by data, with measures: done (choices 210 to 225).
  5. 5. Scheduling strategies that the system can change while it runs (ideas of CFS, EEVDF and sched_ext): done (choices 269 to 283, docs/SCHEDULING.md). Not done: a strategy that a program supplies, a share for each budget.
  6. 6. A block device service (virtio), then the storage service, with a program that keeps its data: done (steps 30 to 32, choices 142 to 159).
  7. 7. The network: a virtio device service, then UDP, TCP, QUIC, with a server as the scenario. The first layer is done (choices 191 to 207): the frame service, the network service with ARP, IPv4, ICMP echo and UDP, endpoints with a grant of one remote. TCP is done (295 to 309): a library in the program over a segment endpoint, client grants and listen grants, the program server. Next: QUIC over datagrams (docs/NETWORK.md, "QUIC").
  8. 8. Functions that give the information of the modules, rich and descriptive, with results that the console combines (owner, 2026-11-10; property 12): done (step 36, choices 172 to 182).
  9. 9. Errors that diagnose: an error record with each failure of the kernel, of a service and of a direct call, the load error of the loader; a stack trace in the report of a fault; a program with one case for each kind of error (owner, 2026-11-10): done (choices 227 to 241).
  10. 10. A program drives its virtual memory: reserve a range of addresses, change a protection, take pages back, large pages: done (choices 432 to 439; not done: the fault ring). Tables of records as parameters between modules: done (417 to 428). Then the counters of the processor for a program, and graphical tools that inspect memory and execution (owner, 2026-11-10).
  11. 11. The name modus in the code, the tool, the bar and the README.
  12. 12. The other gaps of this file: principals, signatures, TPM and Secure Boot, the document service, keyboard layouts.
  13. 13. The interface package, next users, in this order: inspect (a list with a filter and a choice, tables, a hex view), scheduler and server (their own card and large text), the bar, the menus and the window frames of the compositor, the cards of the boot display, then the tables of budget, rights, origin, limits, frames, imports, processors, the measures of crowd, flow, disk, then monitor, manual, notes, tally, terminal. Then the package as a module for each program: the description passes tables as parameters since 416, and interface_through_module uses it (426); the elements that take input need the interface state as a table of one row with the direction Change.
  14. 14. The extended state only at a switch of threads: done (548, 549). Next: skip the restore when a processor takes again the thread whose state its registers hold, with a measure.