Skip to content

Latest commit

 

History

History
1597 lines (1398 loc) · 89.5 KB

API.md

File metadata and controls

1597 lines (1398 loc) · 89.5 KB

TOS kernel API reference

Generated by build/make_apiref.py from the code in tos/kernel/ -- do not hand-edit; change the --- comment above a function and regenerate. 781 public functions in 65 modules.

A function marked admin or root refuses callers below that tier. build/test_apiref.py checks every mark against the gate the function's body calls, and every gate against a mark.

Modules

kernel.audio

tos/kernel/audio.lua

  • audio.success() Single short high beep: success, positive feedback.
  • audio.confirm() Two ascending beeps: login success, task complete.
  • audio.error() Single long low beep: error, login failure.
  • audio.critical() Three short low beeps: critical error, kernel panic.
  • audio.warning() Two short beeps: warning, command not found.
  • audio.notify() Single quick high beep: notification, incoming message.
  • audio.shutdown() Two descending beeps: shutdown, logout.
  • audio.boot() Single short medium beep: boot POST success.
  • audio.bootComplete() Three ascending beeps: boot complete melody.
  • audio.chat() Quick two-tone chime: chat / incoming message.
  • audio.tick() Single low tick: keypress feedback (very short, use sparingly).
  • audio.setEnabled(state) Enable or disable audio feedback.
  • audio.isEnabled() Check if audio is enabled.
  • audio.setVolume(v) Set volume/duration multiplier (0.5 = shorter beeps, 1.0 = normal, 1.5 = longer).
  • audio.getVolume()
  • audio._resetCooldown()
  • audio.init(config) Initialize from system config.

kernel.backup

tos/kernel/backup.lua

  • backup.init(modules)
  • backup.snapshot(srcRoot, destPath, opts) Create a snapshot. Writes the encoded blob to destPath. @return (true, summary) or (false, err)
  • backup.inspect(srcPath, opts) Inspect a backup file. Returns (info, nil) on success, where info is { magic, created, entries, root, dataHash } — does NOT read the entry bodies, so it's safe on large backups.
  • backup.restore(srcPath, opts) Restore from a backup file into destRoot (defaults to the original root recorded in the header). Returns (true, summary) or (false, err).

kernel.bootcfg

tos/kernel/bootcfg.lua

  • bootcfg.echoMinLevel(verbosity)
  • bootcfg.load(fs) Load /etc/boot.cfg, fail-safe. Never raises; an unreadable/corrupt file yields the normal defaults so boot always proceeds. @param fs table anything with exists(path) + readFile(path)
  • bootcfg.save(fs, cfg) Persist a (normalized) config back to /etc/boot.cfg. @param fs table anything with writeFile/writeFileAtomic
  • bootcfg.verbosity(cfg) Effective muter level for this config (explicit override, else profile).
  • bootcfg.shows(cfg, level) True if level should be shown at the config's verbosity. e.g. bootcfg.shows(cfg, "text") is false on a silent/splash boot.
  • bootcfg.wants(cfg, name, ramOK) Should optional feature name load? Resolution order: 1. an explicit advanced override (true/false), 2. the profile's pin for that feature, 3. the RAM gate (ramOK, default true) — today's behavior.
  • bootcfg.ramOK(cfg, detected) Resolve the RAM gate: the operator's declaration wins, else the live measurement. detected is the caller's "free RAM looks fine" boolean.
  • bootcfg.ui(cfg, totalKB) Effective startup interface: "home", "split" or "cli" -- and, as a second value, "auto" when it was chosen FOR the operator rather than set by them. "home" is the merged surface — one tab, tiles and files as two views of it, the prompt resident in both. "split" is the pre-merge shape, kept for operators who want the Desktop as its own tab. Both are the panels TUI; only "cli" is a different program. Unset is AUTO: the panels from PANELS_MIN_KB of installed memory (totalKB), the CLI below it, because below it the panels load and then crash the machine on the first command. ramGate = "plenty" is the operator saying the memory is fine, so it means the panels. With no totalKB (off-box) auto is the panels, as it always was.

kernel.bootsettings

tos/kernel/bootsettings.lua

  • bootsettings.ramLabel(probe) Describe the machine's installed memory for the RAM field's label, e.g. "2x T3.5". Returns nil when we can't tell (off-box unit tests, no component API), in which case the field keeps its generic wording. probe is injectable so the model stays testable without hardware.
  • bootsettings.fields(cfg, ramLabel) Return the editable fields with their current display values for cfg. @return array of { key, label, group, value(display string) } @param ramLabel string|nil installed-memory summary for the RAM row's label (see bootsettings.ramLabel). Omitted → generic wording, which is what the pure field-model tests exercise.
  • bootsettings.cycle(cfg, idx, dir) Advance field #idx of cfg by dir (+1/-1) through its value ring. Mutates and returns cfg. Out-of-range idx is a no-op.
  • bootsettings.cycleKey(cfg, key, dir) Advance the field whose key is key by dir. Lets the runner cycle a field by identity, so the visible (filtered basic/advanced) list never has to map its row back to a full-list index. No-op on unknown key.
  • bootsettings.run(cfg, ctx)

kernel.bootsteps

tos/kernel/bootsteps.lua

  • bootsteps.stageFor(msg) Friendly step for a raw boot message, or nil if none matches.
  • bootsteps.next(prevStage, msg) Dedup helper: the step to SHOW given the previously-shown step and a new message, or nil when there's nothing new (unmatched, or the same step as last time). Lets the caller collapse runs of the same stage into one line.
  • bootsteps.splashGeometry(screenW, screenH, headerBottom)
  • bootsteps.splashFits(screenW, screenH, headerBottom) Does that bar actually fit on that screen? The property the bug broke.

kernel.clipboard

tos/kernel/clipboard.lua

  • clipboard.set(value, seat) Put text on the seat's clipboard. value is a string or an array of lines. Returns (true, truncated) or (false, reason).
  • clipboard.get(seat) The seat's clipboard as an array of lines, or nil when empty. Returns a COPY: a caller that mutates what it pasted must not mutate what is still on the clipboard.
  • clipboard.text(seat) The seat's clipboard as one string, newline-joined, or nil.
  • clipboard.line(seat) The seat's clipboard as ONE line, for a single-line target like the command prompt. Multi-line content is joined with spaces rather than concatenated: gluing "ls" and "cd" into "lscd" would produce a command the operator never typed and might well run.
  • clipboard.isEmpty(seat)
  • clipboard.count(seat) How many lines are held (0 when empty).
  • clipboard.describe(seat) A short human summary for a status row: "3 lines · 214 bytes".
  • clipboard.clear(seat)
  • clipboard.clearAll() Wipe every seat (reboot / test reset).

kernel.compress

tos/kernel/compress.lua

  • compress.init(modules) Detect a data card exposing deflate/inflate. Safe to call repeatedly. Uses the shared kernel.datacard detector so it agrees with crypto and the POST screen about what's installed — and, crucially, reports a card that IS present but lacks deflate/inflate honestly (instead of the old "No data card", which read as a detection failure even when crypto saw the same card as Hardware).
  • compress.available() True when a data card can actually deflate/inflate on this machine.
  • compress.isPacked(blob) True if blob is a TCZ container produced by pack().
  • compress.pack(data) Compress data into a TCZ blob. Always returns a valid blob (never nil for a string): falls back to "stored" when there's no card, the payload is tiny, deflate fails, or the result wouldn't be smaller. @return blob string, method ("deflate"|"stored"), origBytes, packedBytes
  • compress.unpack(blob) Reverse pack(). Returns the original data, or (nil, err). A "stored" blob decodes without a data card; a "compressed" blob needs one.

kernel.config

tos/kernel/config.lua

  • config.init(fsModule)
  • config.save()
  • config.get(key)
  • config.set(key, value)
  • config.getAll()
  • config.isTablet() Check if running in tablet mode
  • config.isHeadless() Check if the system is headless (no GPU/screen). True for server blades by default, but can also be forced via config for computers that act as dedicated service hosts.
  • config.deviceType() Get the device profile name
  • config.reset(key) Reset a key to default

kernel.cron

tos/kernel/cron.lua

  • cron.init(modules)
  • cron.add(name, intervalSec, script, opts)
  • cron.remove(id) Remove a job.
  • cron.setEnabled(id, enabled) Enable/disable a job.
  • cron.list() List all jobs.
  • cron.tick() Execute pending jobs (called by interval timer).
  • cron.shutdown() Shutdown: cancel the timer

kernel.crypto

tos/kernel/crypto.lua

  • crypto.init()
  • crypto.hasHardware()
  • crypto.hash(data)
  • crypto.hashPassword(password, salt)
  • crypto.verifyPassword(password, salt, storedHash)
  • crypto.addEntropy(seed)
  • crypto.exportEntropy(n)
  • crypto.salt(length)
  • crypto.token()
  • crypto.encrypt(data, key) Encrypt data with a shared key
  • crypto.decrypt(data, key, method)
  • crypto.checksum(data)
  • crypto.sign(data, secret)

kernel.datacard

tos/kernel/datacard.lua

  • datacard.capsOf(p, addr)
  • datacard.tierOf(caps)
  • datacard.detect() Detect the first data card. Returns a table: { present=, addr=, proxy=, caps={...}, tier=N, name=string } present=false (with tier 0) when no data component exists.

kernel.diag

tos/kernel/diag.lua

  • diag.run(report, opts) Run every check, calling report(line, severity) for each line. Returns an aggregate { ok = N, info = N, warn = N, err = N }.

kernel.diskmount

tos/kernel/diskmount.lua

  • diskmount.init(deps)
  • diskmount.sanitizeLabel(raw, addr) A disk's user-supplied label as a safe mount-point name. Labels are attacker-controllable (any player can label a disk "../etc"), so only alphanumerics, underscore, hyphen and space survive, runs of spaces collapse, at most 32 characters, and anything left empty (or "." / "..") falls back to "disk_" plus the address stub.
  • diskmount.mount(addr, proxy) Mount disk addr (its filesystem proxy) at /mnt/. Two disks with one label get the second one suffixed with its address stub, and then a number. Never the boot disk; a disk already mounted here answers where it is. @return mountPoint, label | nil, reason
  • diskmount.unmount(addr) Unmount the disk at addr, if it was mounted here. @return mountPoint, label | nil
  • diskmount.mountPointOf(addr) Where disk addr is mounted, and the label it was mounted under.

kernel.display

tos/kernel/display.lua

  • display.isLost() Has the guard tripped (GPU/screen vanished mid-session)?
  • display.init(gpuProxy, width, height)
  • display.getGpuTier()
  • display.getGpuDepth()
  • display.isMonochrome() Returns true if the GPU is monochrome (T1, 1-bit)
  • display.getSize()
  • display.refreshSize() Re-read the GPU's current resolution into the cached W/H. Call after an external setResolution (the screen res command / a program size request) so display.clear() and callers of getSize() use the new dimensions instead of the stale boot values. Returns the refreshed W, H.
  • display.getTheme()
  • display.c(name)
  • display.setTheme(overrides)
  • display.invalidateColors() Forget what colour the hardware is currently set to. The cache above is only correct while EVERY write goes through this module. Anything that touches the GPU directly moves the hardware without moving the cache, and then _setFg/_setBg compare, match, and skip the call they believe is redundant -- so the next fill paints in whatever colour the outsider left behind. That is what makes the status bar go black: not a bad colour, a call that never happened. In practice the outsider is the OpenOS-compat term.gpu() proxy, which forwards set/fill/setBackground/setForeground straight to hardware for any program holding a display capability. Callers that go around this module must call this afterwards; the proxy now does.
  • display.set(x, y, text, fg, bg)
  • display.fill(x, y, w, h, char, fg, bg)
  • display.clear(bg)
  • display.getGpu() Get raw GPU proxy (for emergency shell etc.)
  • display.scrollUp(startRow, endRow) Scroll the screen up by one line, clearing the bottom line
  • display.box(x, y, w, h, title, style) Draw a box with single-line border
  • display.dbox(x, y, w, h, title, style) Draw a double-line box (for emphasis/dialogs)
  • display.hdivider(x, y, w, style)
  • display.menuBar(items)
  • display.statusBar(left, right, row)
  • display.fkeyBar(items, row)
  • display.fit(text, width, align) Truncate or pad a string to fit width
  • display.writeWrapped(x, y, w, maxH, text, fg, bg) Write text within a region, word-wrapping
  • display.dialog(title, message, buttons) Show a centered message dialog
  • display.menuBarEx(items, activeIdx, focusMode, row)
  • display.dropdown(x, y, items, selectedIdx)
  • display.withContext(gpuOverride, wOverride, hOverride, fn) Temporarily swap GPU/resolution context for a function call. Used by screen.displayProxy to delegate high-level drawing to a specific GPU+screen pair without duplicating all the TUI logic.

kernel.ed25519

tos/kernel/ed25519.lua

  • ed25519.publickey(seed) Public key (32 bytes) for a 32-byte secret seed.
  • ed25519.sign(msg, seed, pk) Sign msg with a 32-byte secret seed. Returns a 64-byte signature.
  • ed25519.verify(msg, sig, pk)

kernel.env

tos/kernel/env.lua

  • env.get(process)
  • env.read(process, key) Read a single variable from process env, falling back to defaults.
  • env.write(process, key, value) Write a variable into a process env.
  • env.inherit(parentProcess)
  • env.setDefault(key, value)
  • env.list(process) List all env vars for a process (merged with defaults).

kernel.errors

tos/kernel/errors.lua

  • errors.code(e) "E-401": what an operator reads.
  • errors.hex(e) "0x00040001": derived, never stored.
  • errors.label(e) "E-401 ERR_PERM_DENIED"
  • errors.tag(sym) " [E-401 ERR_PERM_DENIED]", for appending to a message.
  • errors.subsystem(e) "access"
  • errors.find(q) Look an error up by anything an operator might type: E-401, e401, 401, ERR_PERM_DENIED, perm_denied, 0x00040001, or an EEPROM code like C1. nil when it is none of those -- so why ls still means the command.
  • errors.parse(text) The entry a message is tagged with, or nil for an untagged message.
  • errors.biosTable() EEPROM code -> title: the shape srm.BASIC_CODES has always had.
  • errors.all() Every entry, in number order.

kernel.event

tos/kernel/event.lua

  • event.timerErrors()
  • event.on(signal, callback, source)
  • event.once(signal, callback, source) Register a one-time listener (auto-removes after firing)
  • event.off(signal, id) Remove a listener by signal + id
  • event.removeSource(source) Remove all listeners registered by a source
  • event.purgeOwner(pid)
  • event.timer(delay, callback, source)
  • event.interval(delay, callback, source) Schedule a repeating callback
  • event.cancelTimer(id) Cancel a timer
  • event.pull(timeout)
  • event.pullFiltered(filter, timeout) Pull a specific signal type, with timeout
  • event.reservedSignal(name)
  • event.push(...)

kernel.fs

tos/kernel/fs.lua

  • fs.init(bfs)
  • fs.normalize(path)
  • fs.split(path) Split path into directory and filename
  • fs.join(...) Join path segments
  • fs.mount(path, proxy)
  • fs.unmount(path) Unmount
  • fs.exists(path)
  • fs.isDirectory(path)
  • fs.list(path)
  • fs.makeDirectory(path)
  • fs.remove(path)
  • fs.rename(from, to)
  • fs.size(path)
  • fs.lastModified(path)
  • fs.readFile(path) Read entire file as string
  • fs.writeFile(path, content) Write string to file (overwrite)
  • fs.appendFile(path, content) Append to file
  • fs.writeFileAtomic(path, content)
  • fs.recoverAtomic(paths)
  • fs.copyFile(src, dst)
  • fs.copy(src, dst) Copy a file from src to dst (handles cross-filesystem copies)
  • fs.copyRecursive(src, dst)
  • fs.open(path, mode) Open a file handle (low-level)
  • fs.spaceTotal(path)
  • fs.spaceUsed(path)
  • fs.spaceFree(path)
  • fs.mounts()

kernel.hal

tos/kernel/hal.lua

  • hal.stickTier(kb) Tier name for a SINGLE stick of kb, or nil when it isn't a stock size.
  • hal.ramBanks(deviceInfo) How many memory sticks are installed, or nil when the host can't say. Sticks are not components, so component.list("memory") is always empty; getDeviceInfo is the only place they appear.
  • hal.ramSticks(deviceInfo) Per-stick capacities in KB, largest first -- or nil when they can't be worked out. totalMemory() alone cannot tell 2x768K from 1024K+512K (both 1536K over two sticks), which is how a T3.5+T2.5 machine got reported as "tier 3". The clock narrows each stick to two sizes; the machine's total picks between them, since totalMemory() is the plain sum of the installed sizes (OC scales its 64-bit allowance back out of what it reports). The answer must be the ONLY combination that adds up: two sticks always resolve, but from three on two different sets can share a total (2x256 + 2x384 = 2x192 + 512 + 384), and then this says nothing rather than pick one. Non-default call budgets or ramSizes match nothing, and likewise give nil. (test_boot_screen_and_ram.lua) @param deviceInfo table|nil injected for tests; defaults to the live call
  • hal.ramSummary(sticks, totalKB, modules) Human summary of installed memory: "2x T3.5", "T3.5 + T2.5", or an honest capacity when the sticks aren't stock sizes / can't be enumerated. MIXED TIERS ARE THE POINT — never average them into one tier.
  • hal.scan()
  • hal.list(ctype)
  • hal.primary(ctype)
  • hal.proxy(ctype)
  • hal.count(ctype)
  • hal.has(ctype)
  • hal.systemInfo()
  • hal.freeMemory(need) Free memory you can act on, rather than the first number offered. computer.freeMemory() counts UNCOLLECTED GARBAGE as used, so it only ever UNDERSTATES what is available -- never the reverse. On a real machine that is a factor of four at the end of boot: the kernel logged "Free memory: 322KB" and the same box, eighteen seconds later with nothing freed in between, reported 1268KB. Harmless in a log line. Not harmless in a GATE, and four of them read it as fact: whether the optional boot stages load, whether to shout LOW MEMORY, whether the display's dirty-cell shadow is affordable, and what the splash tells the operator they have to work with. Each one was free to answer "no" because of garbage that a collection would have thrown away. The one-directional error is what makes the fix cheap. A reading that ALREADY clears need is true -- that memory really is free, and no collection can produce less of it -- so the common case pays nothing. Only a reading that falls short might be an artefact, and only then is it worth collecting and asking again. Pass no need to mean "I want the honest number" (a report, not a gate) and always collect. Returns (bytes, collected).
  • hal.componentCount()
  • hal.checkWireless()
  • hal.handleComponentAdded(addr, ctype)
  • hal.handleComponentRemoved(addr, ctype)
  • hal.report()

kernel.i18n

tos/kernel/i18n.lua

  • i18n.validCode(code) A valid language code: 2+ lowercase letters, then letters/digits/-/_, 8 chars max ("ru", "es", "pt-br"). Also the filename stem, so the pattern doubles as path-traversal protection.
  • i18n.init(modules)
  • i18n.loadCatalog(code) Load + validate a catalog file (pure-ish: touches only fs). Returns { meta = {...}, strings = {...} } or (nil, reason).
  • i18n.setLanguage(code) Switch the active language. "en"/nil/"" resets to English defaults.
  • i18n.language()
  • i18n.languageName()
  • i18n.available() Scan /usr/lang for installed catalogs. Returns a sorted array of { code, name } (English is always first — it's the built-in default).
  • i18n.t(key, default, ...) Translate. default is the inline English text and the guaranteed fallback. Extra args run through string.format — a broken %-spec in a community catalog falls back to formatting the ENGLISH default, and if even that fails, returns the unformatted default (a wrong- language or unformatted string beats a crash in a draw path).
  • i18n.keysSeen() The (key -> English default) registry gathered this session, as a sorted array of { key, default }. Powers lang dump.

kernel

tos/kernel/init.lua

  • kernel.boot(opts)
  • kernel.loginAndStartShell()
  • kernel.crashWriteRaw(name, body, away, marker)
  • kernel.crashDump(reason, detail)
  • kernel.checkLastCrash()
  • kernel.monitorSnapshot(displayIdx, seatSession)
  • kernel.setMonitorHost(enable)
  • kernel.isForeground()
  • kernel.monitorList(displayIdx, seatSession)
  • kernel.monitorAct(action, id)
  • kernel.taskSwitcher(displayIdx, seatSession, opts)
  • kernel.emergencyShell()
  • kernel.verifySystem(printFn)
  • kernel.computeManifestHash() Compute the running manifest's SHA-256 hex digest. Returns (hexDigest, nil) on success, (nil, errMsg) on failure.
  • kernel.anchorManifestHash() Write the current manifest hash into the EEPROM data field. Operator runs this once after a clean install (or after a verified upgrade); doctor reports any later mismatch (see the note above: boot does not check it).
  • kernel.clearManifestAnchor() Remove the anchor, keeping the boot address. Used when an operator is about to upgrade (the hash will legitimately change) or wants to drop the check entirely.
  • kernel.verifyManifestHash() Verify the running manifest's hash against the EEPROM-anchored value. Returns (true) on match, (false, reason) on mismatch or absence. The kernel boot path calls this once and either continues or halts based on the result.
  • kernel.getLog()
  • kernel.getHAL()
  • kernel.getEvent()
  • kernel.getProc()
  • kernel.getFS()
  • kernel.getDisplay()
  • kernel.getDisplayIdx()
  • kernel.uptime()
  • kernel.shutdown(reboot)
  • kernel.reboot()
  • kernel.version()
  • kernel.headlessMain()

kernel.internet

tos/kernel/internet.lua

  • internet.init(modules)
  • internet.parseUrl(url) Validate a URL for use with request(). Returns ok, scheme|err, host.
  • internet.hostOf(url) The host part of a URL, lower-cased, or nil.
  • internet.status() Is internet access usable right now? Reports WHY not, because the three reasons an operator hits are genuinely different problems: no card installed, the mod's server config has HTTP off, or an admin here has switched it off in /etc/tos.cfg.
  • internet.available()
  • internet.isEnabled() Machine-wide kill switch. A card is installed by a deliberate physical act, so the default when one is present is ON — requiring both a card and a config toggle is friction without a matching gain. The switch exists for the shared box where the card is wanted for one service and not for everybody.
  • internet.get(url, opts) Fetch a URL into a STRING. For small things — an index, a status page. Bounded by maxBytes (default 64 KB) because the result is RAM. Returns body, err, meta.
  • internet.download(url, destPath, opts)
  • internet.socket(address, port) Raw TCP socket, for the few things that are not HTTP. Returns the OC socket handle untouched — there is no useful bounding to apply to a stream whose protocol we do not know, so this is deliberately thin and the internet capability is the whole of the gate.

kernel.jbod

tos/kernel/jbod.lua

  • jbod.makePool(members)
  • jbod.loadConfig(fsModule)
  • jbod.saveConfig(fsModule, cfg)

kernel.keychain

tos/kernel/keychain.lua

  • keychain.init(modules)
  • keychain.unlock(masterPass, session) Unlock the keychain for the current session. masterPass is typically the user's login password. Returns (true) on success, (false, err) otherwise.
  • keychain.lock(session) Clear the unlocked map for session.
  • keychain.isUnlocked(session) Returns true iff the keychain is currently unlocked for the given (or current) session.
  • keychain.set(name, passphrase, session) Set a slot. name is a short identifier ("home-tape", "work-floppy"). passphrase is the secret to stash. Both must be strings.
  • keychain.get(name, session) Retrieve a slot. Returns the passphrase or (nil, "no such slot").
  • keychain.remove(name, session) Remove a slot.
  • keychain.list(session) List slot names (no passphrases).
  • keychain.rekey(oldMaster, newMaster, session)

kernel.log

tos/kernel/log.lua

  • log.init(opts)
  • log.setLevel(level)
  • log.setSourceLevel(source, level) Set the minimum level for a single source label. Pass nil to clear.
  • log.debug(source, msg)
  • log.info(source, msg)
  • log.warn(source, msg)
  • log.error(source, msg)
  • log.fatal(source, msg)
  • log.recent(count, session)
  • log.getSourceLevels() Returns the list of sources currently filtered (for log filter UI).
  • log.format(entry)
  • log.detachEarlyPrint()
  • log.attachFile(fs, path, opts)
  • log.detachFile() Detach (stop persisting). Used during kernel.shutdown so the final close happens cleanly and a half-rotated file isn't left behind.
  • log.flush() Append entries that arrived since the last flush. Safe to call repeatedly — only new entries are written.
  • log.fatal(source, msg)

kernel.logo

tos/kernel/logo.lua

  • logo.banner(opts) The banner as role-tagged, pre-padded lines for a LINE-BASED caller (the boot splash, the installer). role ∈ "mark"|"tagline"|"vendor"|"motto"| "blank". opts: { ascii=bool, compact=bool (drop vendor/motto), indent=N (left-align instead of centre), width=N }.

kernel.monitor

tos/kernel/monitor.lua

  • monitor.describe(p)
  • monitor.kindTag(p)
  • monitor.memBar(used, total, width)
  • monitor.fmtUptime(sec)
  • monitor.buildRows(procs, services)
  • monitor.nextSelectable(rows, sel, dir)
  • monitor.svcStatus(s) One service's state in words, for every view that lists services. A service whose need is missing (rc's needs, e.g. no network card) was never loaded and cannot be started; the Monitor tab, the CLI switcher and this text listing each said "stopped", while service said why. (test_monitor.lua)
  • monitor.svcToggle(rcMod, s, who) Start a stopped service or stop a running one, for the Monitor tab and the CLI switcher. Returns true, or false and the reason. rc.start and rc.stop REFUSE by returning (false, why); both callers used to treat any call that did not raise as done, so Enter on a service that could not load said "Starting 10-discoveryd" and logged "Started" for a service that never ran. (test_monitor.lua)
  • monitor.textRows(procs, services)
  • monitor.canAct(seatTier, seatUser, displayIdx, p)
  • monitor.firstSelectable(rows)

kernel.net.aliases

tos/kernel/net/aliases.lua

  • aliases.init(modules)
  • aliases._load()
  • aliases.set(alias, address) — admin Register alias → address. Replaces any existing mapping for either side (so renaming a host or repointing an alias is one call).
  • aliases.remove(alias) — admin Drop an alias by name (case-insensitive).
  • aliases.addressOf(alias) Look up an address by alias (case-insensitive). Returns the full address string, or nil.
  • aliases.aliasOf(address) Reverse lookup: alias for an address, or nil.
  • aliases.list() All aliases as an array of { alias, address } records, sorted by alias for stable display.
  • aliases.resolve(s) Resolution helper used by every network-aware command. Accepts EITHER: * a known alias (case-insensitive) → returns the bound address * a full UUID (36 chars with dashes) → returns it verbatim * a partial UUID prefix that uniquely matches ONE known alias address — returns the canonical full address Returns (nil, reason) when ambiguous or unresolved.
  • aliases.format(address) Pretty-print an address: returns " (xxxxxxxx...)" if aliased, else just the truncated address. Used by net/chat/cluster status output for readability.

kernel.net.chatpair

tos/kernel/net/chatpair.lua

  • chatpair.init(opts)
  • chatpair.startWindow() Open a 5-minute pairing window. Returns (code, expires_at_uptime). Caller displays the code to the operator; the operator types it on the other peer.
  • chatpair.closeWindow()
  • chatpair.windowOpen()
  • chatpair.windowInfo()
  • chatpair.onPairInit(packet, from) Receiver-side handler: inbound CHAT_PAIR_INIT. Verifies the MAC against our window secret, installs the secret locally, and sends CHAT_PAIR_CONFIRM back so the sender can do the same on its side.
  • chatpair.connect(peer, code, timeout) Pair with peer using the out-of-band code shown by the receiver. Sends CHAT_PAIR_INIT, awaits CHAT_PAIR_CONFIRM, on success installs the derived secret locally too. Returns (true) on success or (false, reason) on failure.

kernel.net

tos/kernel/net/init.lua

  • net.portOwner(port) Whose port port is in the ecosystem registry, or nil.
  • net.init(modules)
  • net.getChatPair() Expose the chat-pair module so shell commands can call connect/startWindow.
  • net.meshAvailable() True when the mesh transport came up (network hardware present).
  • net.meshSend(opts) Send a mesh message. opts = { svc="mail"|..., to=address|"*", user=, fromUser=, payload=, ttl=, allowPlaintext= }. Returns (id, sealed) or (nil, reason). #SEC — a unicast send with no shared secret is REFUSED unless allowPlaintext is set (relayed plaintext was the review's "silent fallback" finding).
  • net.meshOn(svc, handler) Register the local delivery handler for a mesh service kind. handler(message, env) -> truthy when accepted (only then is the delivery ACKed back to the sender).
  • net.meshOff(svc) Unregister a mesh service handler.
  • net.meshHasHandler(svc) Is a local handler registered for this service kind?
  • net.meshPending() Count of our sent mesh messages still awaiting acknowledgement.
  • net.meshTick() Re-flood un-ACKed messages whose retry window has come (throttled to ~5s). Safe to call often; a no-op when the mesh isn't available.
  • net.isAvailable()
  • net.getAddress()
  • net.getHostname()
  • net.setHostname(name) Change the name HELLO announces. net reads it from tos.cfg once, at init, so the shell's hostname <name> (admin) calls this after saving; until then the old name went out until the next boot. Not on the sandbox's net facade: renaming the machine is not a program's call.
  • net.modemCount() Return count of available modems.
  • net.send(address, packet, port)
  • net.broadcast(packet, port)
  • net.handleIncoming(remoteAddr, port, distance, rawData)
  • net.setServiceArm(name, on)
  • net.getServiceArm(name)
  • net.on(msgType, callback)
  • net.off(msgType, id) Remove a listener by message type + ID
  • net.onceFrom(msgType, addr, callback) Register a one-shot listener that fires ONLY for msgType packets received from addr. Subsequent packets (duplicates, late arrivals, spoofs from other peers) are silently ignored — but the listener itself stays registered until removed via net.off / net.offAll. The internal fired flag does the de-duplication; we don't self-deregister inside the callback because dispatchToListeners iterates with ipairs() and a mid-iteration table.remove would skip or repeat sibling listeners. @return number listener id (pass to net.off or net.offAll)
  • net.offAll(entries) Bulk-remove a set of listeners. Pass an array of { type = msgType, id = listenerID } entries — the same shape the request-reply call sites collect as they install listeners. Safer than three or four sequential net.off() calls because partial cleanup on an early send-failure leaves dangling listeners that fire on later, unrelated traffic.
  • net.waitFor(predicate, timeout)
  • net.verifyPeer(addr, timeout, force) Verify a peer's identity via challenge-response. Sends CHALLENGE, awaits CHALLENGE_RES, validates the HMAC proof. Returns true if the peer demonstrably holds the shared secret; false + reason otherwise. Pre-conditions: • Peer must be at TRUSTED level (lower trust = no shared secret to verify against). • A shared secret must be set with the peer. Use force = true to bypass the positive cache.
  • net.invalidateVerification(addr) Invalidate the challenge-response cache for one peer (or all peers if addr is nil). Use after a peer's secret rotates, after a suspected key compromise, or to force re-verification before a particularly sensitive operation.
  • net.discover() Broadcast a ping to discover other TOS machines
  • net.hello(address) Send hello to a known peer (exchange hostnames)
  • net.sendMessage(address, text) Send an encrypted message to a trusted peer
  • net.requestTrust(address) Request trust from a remote machine
  • net.getTrust() Get the trust manager instance
  • net._recordPeer(addr, payload) Internal: record a peer from a pong response.
  • net.peers() Return the current discovered-peer table as a sorted list.
  • net.findPeer(query) Look up a specific peer by address or hostname.
  • net.scan(timeout) Active scan: broadcast a ping, collect pong responses for timeout seconds, return the peers found in this scan.
  • net.getProtocol() Get the protocol module
  • net.status() Get network status summary
  • net.shutdown()
  • kernel.net.mesh

    tos/kernel/net/mesh.lua

    • mesh.newEnvelope(from, to, fields) Build a fresh outbound envelope. id MUST be unique per message; the net layer derives it from (boot epoch + a monotonic counter). Falls back to from#seq so the pure tests can build deterministic ids.
    • mesh.newAck(env, selfAddr, fields) Build the ACK an origin expects back once delivery succeeds. The ACK floods back addressed to the original sender's node.
    • mesh.newSeen(max)
    • mesh.sawBefore(seen, id) Record id; return true if it had been seen before (a duplicate). Evicts the oldest id once the cache is full so memory stays bounded.
    • mesh.seenKey(env) The dedup key of an envelope: its id, plus its resend counter when it has one. A sender re-sending a message bumps the counter, so relays that saw the first flood pass the re-send on; the RECEIVER dedups delivery by the id itself (meshctl's delivered set).
    • mesh.inPath(env, addr) True if addr already appears in the envelope's traversed path.
    • mesh.route(env, selfAddr, isForMe, seen) Decide what a node at selfAddr should do with an arriving envelope. isForMe(env) -> bool: the net layer resolves whether env.to targets this node (its address, hostname, or an alias of it). seen is this node's dedup cache. Returns { dup, deliver, forward, out } where: dup this id was already processed (everything else false) deliver hand the message to the local mailbox / ack handler forward re-broadcast out (ttl already spent, path extended) out the envelope to re-broadcast (only when forward is true)
    • mesh.newOutbox()
    • mesh.enqueue(ob, env, opts, now) Queue an envelope for retry. opts: interval seconds between re-floods (default RETRY_EVERY) deadline absolute time after which we give up (default: never) relay true if we're only holding a passed-through copy (shorter default deadline so relays don't hoard forever) canHold (relay) function(bytes) -> may this node hold that much more? Default: no more than RELAY_HOLD_BYTES. now stamps the first attempt window. A second enqueue of the same id is ignored (keeps the original schedule). A relay copy is held only if it fits, and only the newest RELAY_HOLD_MAX are (see above).
    • mesh.ack(ob, id) An ACK arrived (or we delivered locally): stop retrying this id.
    • mesh.ackRelay(ob, id) A passing ACK: a relay copy of what it acknowledges need not be re-flooded. Never an origin's own copy -- that takes an ACK the origin can verify (meshctl), not anything that merely says "ack".
    • mesh.shedRelays(ob, keep) Drop relay copies, oldest first, until at most keep remain. Returns how many went. They are a courtesy, so memory pressure takes them first.
    • mesh.dueItems(ob, now) The outbox entries due at time now, their schedules advanced; any past their deadline are dropped. Returns (items, droppedCount).
    • mesh.due(ob, now) Return the envelopes due for (re-)flooding at time now, advancing their schedules, and drop any past their deadline. Caller broadcasts whatever this returns.
    • mesh.pending(ob) How many messages are still awaiting delivery (diagnostics / mail UI).

    kernel.net.meshctl

    tos/kernel/net/meshctl.lua

    • meshctl.init(modules)
    • meshctl.sealEnv(env, secret)
    • meshctl.openEnv(env, secret)
    • meshctl.compose(opts)
    • meshctl.ingest(env, selfAddr, isForMe, seen, secretFor)
    • meshctl.new(deps) Create a controller. deps: myAddr string this node's modem address broadcast function(envelope) flood an envelope to neighbours secretFor function(addr) -> secret|nil shared secret with a peer clock function() -> number monotonic seconds isForMe function(env) -> bool (optional) does env.to target us freeMemory function() -> bytes (optional) sizes the relay holds store { load = fn() -> state, save = fn(state) } (optional) log table (optional)

    kernel.net.protocol

    tos/kernel/net/protocol.lua

    • protocol.makePacket(msgType, payload, opts)
    • protocol.serialize(packet) Serialize a packet to a string for transmission
    • protocol.deserialize(str)
    • protocol.validate(packet) Check if a deserialized table is a valid TOS packet
    • protocol.ping()
    • protocol.pong()
    • protocol.hello(hostname, version)
    • protocol.helloAck(hostname, version)
    • protocol.deny(reason)
    • protocol.error(message)
    • protocol.message(text, encrypted)
    • protocol.challenge(nonce)
    • protocol.challengeResponse(nonce, proof)
    • protocol.trustRequest(hostname)

    kernel.net.remote

    tos/kernel/net/remote.lua

    • remote.setEnabled(v)
    • remote.isEnabled()
    • remote.stepBudgetAvailable() Can the CPU/memory budget actually be enforced on this host? False on OpenComputers. Exposed so diagnostics can say so out loud rather than implying a guarantee that is not there.
    • remote.init(modules)
    • remote.execute(address, command)
    • remote.handleExec(packet, fromAddr)

    kernel.net.transfer

    tos/kernel/net/transfer.lua

    • transfer.setEnabled(v)
    • transfer.isEnabled()
    • transfer.init(modules)
    • transfer.request(address, remotePath, localPath, opts)
    • transfer.handleRequest(packet, fromAddr)

    kernel.net.trust

    tos/kernel/net/trust.lua

    • trust.init(modules)
    • trust.getLevel(address)
    • trust.getPeer(address) Get peer info
    • trust.isAllowed(address, msgType) Check if a specific message type is allowed from an address
    • trust.seen(address) Record that we've seen a peer (updates lastSeen, creates entry if new)
    • trust.setLevel(actor, address, level, actorTier)
    • trust.trustKnown(actor, address, actorTier) Convenience: trust a peer at KNOWN level
    • trust.trustFull(actor, address, actorTier) Convenience: trust a peer at TRUSTED level
    • trust.block(actor, address, actorTier) Block a peer
    • trust.revoke(actor, address, actorTier) Revoke trust (reset to UNKNOWN)
    • trust.forget(actor, address, actorTier) Remove a peer entirely from the database
    • trust.setSecret(actor, address, secret, actorTier) — admin
    • trust.getSecret(actor, address, actorTier) — admin
    • trust.generateSecret(actor, address, actorTier) — admin Generate and set a new shared secret (both sides need to do this).
    • trust._internalGetSecret(address, token)
    • trust.getBootstrapToken()
    • trust.addPendingRequest(address, hostname) Record an incoming trust request (displayed to user)
    • trust.getPendingRequests() Get all pending trust requests
    • trust.clearPendingRequest(address) Clear a pending request
    • trust.listPeers() List all known peers
    • trust.stats() Count peers by trust level
    • trust.levelName(level)

    kernel.netfs

    tos/kernel/netfs.lua

    • netfs.init(modules)
    • netfs.setEnabled(v)
    • netfs.isEnabled()
    • netfs._validateExports(cfg) Validate an export table. Returns (list, err); on any bad entry the WHOLE config is rejected rather than partially applied — a typo that silently widened one export's ACL is the failure mode to avoid.
    • netfs.setExports(list)
    • netfs.getExports()
    • netfs.loadExports(fsModule, serializeMod) Load /etc/netfs-exports.cfg. The file is serialized DATA, never load()'d as code — same rule jbod.lua's config follows.
    • netfs.arm(on) Arm or disarm serving. The netfsd service calls this through net.setServiceArm("netfsd", on): at once if this module is loaded, from the module's own self-init if it loads later. Arming re-reads the export file, so editing it and restarting netfsd is the whole update procedure; a file that does not load leaves NOTHING served, never an earlier, wider table still in memory. Disarming drops the table.
    • netfs._findExport(name)
    • netfs._accessOk(export, addr, wantWrite) Is addr allowed to use this export, for the access it wants? An empty allow list denies everyone: an export with no ACL is a misconfiguration, and the safe reading of a missing rule is "no".
    • netfs._confine(export, rel) Map a share-relative path to an absolute one INSIDE the export root. This is the security boundary (SECURITY 4): normalize first so that .. is resolved rather than pattern-matched, then require the result to sit under the root with a path-separator boundary — otherwise an export of /srv/pub would also grant /srv/public.
    • netfs._dispatch(op, p, fromAddr)
    • netfs.handleRequest(packet, fromAddr) Network entry point. One gate for every op (see SECURITY 1–3, 5).
    • netfs.attach(hostAddr, share, opts) Attach a remote share. @param hostAddr string the exporting machine's modem address @param share string export name on that machine @param opts table { transport = fn } to bypass the network (tests) @return proxy filesystem-component-shaped; mount it, or hand it to jbod.makePool as a member

    kernel.netrepair

    tos/kernel/netrepair.lua

    • netrepair.deps(d) The collaborators, injectable for tests. Kernel callers get the raw fs: this runs behind a ROOT gate at the shell, and its writes are confined to manifest paths it has verified.
    • netrepair.sourceRef(build, override) Which ref to fetch from, and how to describe it.
    • netrepair.urlFor(ref, path)
    • netrepair.isOperatorState(path) Operator state: not ours to "restore". See rule 4.
    • netrepair.damaged(deps) What is broken, measured against the manifest's digests. @return damaged { {path, hash, reason, critical} }, skipped { {path, why} }
    • netrepair.trustManifest(deps, opts) May the manifest be trusted? (rule 1) @return ok, why
    • netrepair.plan(deps, opts) The plan: what would be fetched, and from where. Touches nothing and fetches nothing (rule 5).
    • netrepair.run(deps, opts) Stage, verify, then replace (rules 1-4). Without opts.apply this is the plan and nothing more. @return report { lines, fixed, staged, rejected, ok }

    kernel.notify

    tos/kernel/notify.lua

    • notify.sanitize(spec) Sanitize a caller's spec into a notice. Pure; returns notice or nil+why. Everything is clamped rather than rejected where clamping is meaningful — a program with a slightly-too-long message should still reach the operator, because the message is usually the point.
    • notify.post(spec, clock) Raise a dialog on every seat's next idle tick. @param spec { message=, title=, from=, buttons=, style=, ttl= } @param clock optional injected uptime (tests) @return id, nil | nil, reason Failure is NORMAL here and callers must handle it: a refused post means the operator is already being interrupted enough. The message is still logged, so nothing is lost — it just doesn't get to interrupt.
    • notify.highWater() Highest sequence number issued. A shell starting mid-life uses this so it doesn't replay a backlog the operator already dealt with elsewhere.
    • notify.pending(afterSeq, clock) Notices newer than afterSeq that are still live. Pure w.r.t. the caller: it does NOT consume anything, so every seat sees each notice. @return list, highSeq
    • notify.nextToShow(list, lastShownAt, clock) The one notice a shell should raise right now, or nil. Pure given its arguments — this is the whole display policy, so it is testable without a clock or a screen. @return notice|nil, why
    • notify.settle(id, buttonIndex) Record which button the operator chose, and drop the notice. Called by the shell that raised it. Dropping here (rather than when it is READ) is what lets several seats show the same notice: the first one answered settles it for everybody, which is the right semantics for "the facility is on fire" and harmless for the rest.
    • notify.result(id) What the operator picked for a notice this program posted, or nil if they haven't answered (or it expired unanswered). Data, deliberately — not a callback. A callback posted by a service would have to RUN in the shell's context, which is a privilege boundary this facility has no business crossing.
    • notify.sweep(clock) Drop expired notices. Called opportunistically by the shell drain; also keeps results from growing without bound on a long uptime.
    • notify.depth() How many notices are waiting (for doctor / the Monitor).
    • notify._reset() Test/reset hook: forget everything.

    kernel.pipe

    tos/kernel/pipe.lua

    • pipe.create()
    • pipe.parse(input)

    kernel.pkg

    tos/kernel/pkg.lua

    • pkg.repoHints(m) The repo URLs a manifest names, for itself and for what it requires, in order and once each. Not validated here: validateManifest has, for anything that installed.
    • pkg.parseCap(s)
    • pkg.init(deps)
    • pkg.scan()
    • pkg.list()
    • pkg.info(name) Full manifest for a single package (or nil if not installed).
    • pkg.isEnabled(name)
    • pkg.setEnabled(name, on, opts) — admin
    • pkg.commands() Merged map of shell command name → script path, restricted to enabled packages. Used by the shell PATH resolver instead of (or in addition to) the alphabetic /usr/bin scan.
    • pkg.ownerOfCommand(name)
    • pkg.runCaps() The coded default set, as a copy. Exists so nothing has to keep a SECOND hand-written list of grantable facets in step with this one — TOS-Extras' manifest lint did, and had already drifted: it rejected internet, which pkg has granted since the internet-card round, so a correct manifest would have failed the lint. A mirror of a security allowlist is a mirror that goes stale silently.
    • pkg.reloadCapConfig() Reload /etc/pkg_caps.cfg. Sibling of sandbox.reloadComponentConfig, and reached the same way (component reload-caps). Already-loaded package entries keep the cap set they were built with — flush the command cache (or reboot) for a change to reach a package that has already run.
    • pkg.capAllowed(pkgName, facet) Is facet permitted for package pkgName? Single auditable answer, so every caller (the loader, pkg info, the install-time report) agrees. @return boolean allowed, string|nil reason reason is set only on a refusal
    • pkg.capsForFile(path) The caps a program FILE that a package installed runs with, and that package's name -- or nil when no enabled package owns path.
    • pkg.ownerOfFile(path) Which installed package ships path, enabled or not -- or nil.
    • pkg.noteServiceStarted(svcName, opts) — admin service start <svc> succeeded: enable the package that ships its rc.d script, if one does and it is disabled. Returns that package's name when it changed anything, else nil.
    • pkg.setDispatchEnabled(on)
    • pkg.dispatchEnabled()
    • pkg.getCommand(name) Resolve a shell command name to a callable provided by an enabled package, or nil. The package entry (returning { commands = { name = fn } }) is loaded once and cached. The shell's command dispatcher calls this as a fallback after its built-ins.
    • pkg.getCommandScreen(name) The screen-size request declared by the package that provides name, or nil. Shape: { width=, height=, mode="exact"|"min" }. The shell uses this to fit the display before running the command and restore it afterwards.
    • pkg.getCommandFullscreen(name) Does the package that provides name declare it a FULL-SCREEN program (manifest fullscreen = true)? Such a command is handed the seat as its own process so the operator can suspend it and go back to the shell; everything else keeps running inline in the shell exactly as before. Opt-in on purpose: a third-party package that has never heard of this is unaffected until it asks.
    • pkg.getCommandBackground(name)
    • pkg.flushCommandCache(pkgName) Drop the loaded-entry cache (e.g. after uninstall/disable so a stale command can't keep running). Internal; the lifecycle calls it.
    • pkg.capabilities(name) Parsed capabilities for a package. Returns an array of structured entries ({facet=, scope=}); malformed strings are dropped with a warning rather than failing the whole call.
    • pkg.compareVersion(a, b) Compare two version strings. Returns -1, 0, 1 like strcmp.
    • pkg.satisfiesConstraint(version, constraint)
    • pkg.checkRequires(name)
    • pkg.resolveInstallOrder(targetName, lookup) Resolve install order for a target package, given a lookup function lookup(name) -> manifest that knows about both already-installed packages and packages-available-to-install. Returns (order, err) where order is an array of package names in the order they should be installed. Cycles cause an error.
    • pkg.criticalFiles() Boot-critical files: union of every package's critical[] array. Used by /init.lua's stage-4 verification and synced to the on-disk backup so a corrupted tos-core/package.lua doesn't brick boot.
    • pkg.syncCriticalBackup() Sync the critical-files list to /etc/critical.bak. The backup is read by /init.lua at boot if the primary list (tos-core's package.lua) is missing or corrupt — and fed through one more fallback to a hardcoded minimal list if the backup is also gone. Three layers of defense against griefing or disk corruption. Only writes if the list has actually changed, to reduce write wear on filesystems and to avoid a useless metadata churn on every boot.

    kernel.pkgops

    tos/kernel/pkgops.lua

    • pkg.foreignDefaultCaps() Capabilities a foreign package gets when its manifest declares none. Pure; exposed for the tests.
    • pkg.findConflicts(m, ignoreName) Conflicts between m and what is installed. @return list of { kind = "declared"|"file", other = name, detail = ... }
    • pkg.outdated(opts) Packages with a newer version available. Pure w.r.t. side effects. @return { { name=, from=, to=, dir=, root= }, ... }, sorted by name
    • pkg.upgrade(name, opts) — admin Upgrade one installed package to the newest version on offer. opts.force proceed past conflicts / allow a downgrade opts.allowUnverified same meaning as install's @return ok, summary | false, err
    • pkg.forgetModules(paths) Drop from the module cache every module the kernel loader read from one of paths (package.loadedFrom, kept by /init.lua), so the next require reads the file on disk. Code still running keeps the table it already holds. An rc.d service loads its package's library through the kernel loader (sandbox.lua, allowUserLibs), which keeps it for the whole boot. On the headless machine a service restarted after pkg upgrade ran its new script against the OLD library ("2.0.0/1.0.0") until a reboot. Only modules recorded as coming from those exact files are dropped, never a name: a package shipping /usr/lib/computer.lua cannot make the real computer be forgotten. Returns how many were dropped. (test_pkg_lifecycle.lua)
    • pkg.describeConflicts(list) One-line summary of a conflict list, for an error message.
    • pkg.verifyLicenseKey(manifest, userKey)
    • pkg._verificationGate(m, allowUnverified)
    • pkg.install(srcDir, opts) — admin
    • pkg.runtimeRefusal(m)
    • pkg.describeContradictions(list) Contradictions as one line for a refusal.
    • pkg.plan(repoDir, targetName, opts) Work out an install of targetName without doing any of it. Sources are searched as installWithDeps describes. No side effects. @return plan | nil, err plan.order names, dependencies first (resolveInstallOrder's) plan.dirs name -> the package directory chosen for it plan.expectKey name -> the publisher key install() must verify plan.contradictions { { kind=, name=, text= }, ... } in the order met
    • pkg.installWithDeps(repoDir, targetName, opts) — admin
    • pkg.uninstall(name, opts) — admin
    • pkg.repoRoots() Every directory pkg will look in for installable packages: the default repo roots plus every mounted disk and its immediate subdirectories. Exported because the PICKER needs the identical enumeration and had written its own — which is how it came to miss boot-time mounts (see the note in mountedRepoRoots) and therefore never found the set manifest that tells it about the OTHER floppy. Two implementations of "where are the packages" is one too many.
    • pkg.listRepo(repoDir) List packages available in a repository directory. Returns an array of { name, version, description, source } entries.
    • pkg.findInRepos(targetName, extraRoots) Search every configured repo root + every mounted floppy for a package by name. Returns the first match's directory (or nil).
    • pkg.planByName(targetName, opts)
    • pkg.installByName(targetName, opts) — admin Install one or more packages by name, looking them up in configured repos + mounted media + extraRoots. Resolves deps across all available sources. Returns (true, summary) or (false, err).
    • pkg.computeLicenseHash(packageName, userKey)
    • pkg.listAllAvailable(opts)
    • pkg.listRemoteAvailable(opts) What the configured remote repos advertise. Empty when there is no internet card, no repos, or nothing reachable -- never an error.
    • pkg.installFromFloppy(opts) — admin One-shot helper: scan every mount under /mnt/ for packages, and prompt the caller via a confirm(pkgName, dir) callback before installing each. Used by the floppy-detection panel command.
    • pkg.runInstaller(opts) Run the Optional Utilities TUI picker — the SAME installer that ships on the disk (PICKER_SRC above), so pkg install (no argument) offers the full-screen category menu instead of a sequence of y/N prompts. We run the TRUSTED embedded copy rather than the disk's install.lua: it's byte-identical (pinned by test_picker_sync), always present, and — unlike arbitrary code on untrusted media — safe to execute at the shell's full privilege. A corrupt or malicious disk installer can't hijack this path; a third-party disk that ships its OWN installer is a future question (running untrusted install code at full priv is exactly what we don't want to do implicitly). The picker requires a reachable GPU for its TUI; on a GPU-less machine it degrades to its own line mode, which needs io.read (absent in the panels shell). The caller (pkg install) treats ANY failure — load error, no usable front-end — as a signal to fall back to the prompt-based installFromFloppy path. Returns (true) if the picker ran, or (false, reason).
    • pkg.exportDisk(targetDir, opts) — admin Build an Optional Utilities (add-on) disk from the installed packages. opts.only = { "tetris", ... } restricts to named packages (default: all). @return ok, summary | false, err
    • pkg.knownRepos() Repos this machine has LEARNED of from what it installed: never configured, never trusted. { {url=, from={pkg,...}}, ... }
    • pkg.forgetRepo(which, opts) — admin Forget learned repos: a number from pkg repo list, a URL, or "all".
    • pkg.trustList() Trusted publisher keys: { {label=, key=, fingerprint=}, ... }
    • pkg.trustAdd(label, pubHex, opts) — admin
    • pkg.trustRemove(label, opts) — admin
    • pkg.trustRequire(on, opts) — admin
    • pkg.trustRequired()
    • pkg.discoveredPolicy() What may be taken from a repo nobody configured: "tofu", "trusted" or "off" (kernel.pkgsign, DISCOVERED). "off" when signatures are unavailable.
    • pkg.trustDiscovered(v, opts) — admin
    • pkg.checkSignature(srcDir) Verify (without installing) whatever is in srcDir. This is the pkg verify-sig <dir> path: an operator should be able to ask who signed a floppy BEFORE deciding to install from it.
    • pkg.signPackage(srcDir, passphrase, opts) — admin
    • pkg.signingKey(passphrase, label) The public key a passphrase would sign as — so a publisher can print their own key to hand out without signing anything.
    • pkg.repos()
    • pkg.addRepo(name, url, description, opts) — admin Add/remove a repo. #SEC CR-5 — same admin gate as install: a repo entry is a standing decision about where this machine will accept code from, which is at least as consequential as one install.
    • pkg.removeRepo(name, opts) — admin
    • pkg.searchRemote(opts) Everything available across the configured repos.
    • pkg.installRemote(name, opts) — admin

    kernel.pkgremote

    tos/kernel/pkgremote.lua

    • pkgremote.init(modules)
    • pkgremote.repos() Load configured repos. Returns an array of { name, url, host }. A malformed entry is dropped rather than failing the whole file: one bad line should not make every other repo unreachable.
    • pkgremote.addRepo(name, url, description) Add (or replace) a repo. The caller is responsible for the admin gate — pkg's repo verb does it, in the same place it gates install.
    • pkgremote.removeRepo(name)
    • pkgremote.known() Learned repos, oldest first: { {url=, from={pkg,...}}, ... }
    • pkgremote.learn(urls, from) Record the repos from (a package name) names. A configured repo is not recorded -- it is already more than known -- and a URL that is already known gains from. Returns how many were new.
    • pkgremote.forget(which) Forget learned repos: "all", the number pkg repo list shows, or a URL.
    • pkgremote.index(repo, opts) Fetch and parse a repo's programs.cfg. Cached for the session so a multi-package install doesn't re-download the index per package. Returns index, err.
    • pkgremote.clearCache()
    • pkgremote.search(opts) Every package available across the configured repos. Returns an array of { name, repo, description, version, files }.
    • pkgremote.fetch(name, opts) Download name from the configured repos into a local staging tree laid out like a repo directory, and return the PACKAGE DIRECTORY inside it for pkg.install to consume. Returns pkgDir, err.
    • pkgremote.cleanup(pkgDir) Drop the staging tree. Called after an install so downloaded sources do not sit on a small disk forever.

    kernel.pkgsign

    tos/kernel/pkgsign.lua

    • pkgsign.init(deps)
    • pkgsign.fingerprint(pubHex)
    • pkgsign.reloadTrust()
    • pkgsign.listKeys() Trusted publishers as { {label=, key=, fingerprint=}, ... }, sorted.
    • pkgsign.requiresSignature()
    • pkgsign.labelForKey(pubHex) The label this key is trusted under, or nil.
    • pkgsign.addKey(label, pubHex) Trust pubHex under label. The CALLER is responsible for the admin gate — see pkg.trustAdd, which owns it so there is one gate and not two implementations of the same rule.
    • pkgsign.removeKey(label)
    • pkgsign.setRequireSignature(on)
    • pkgsign.discoveredPolicy() "tofu", "trusted" or "off": see DISCOVERED above.
    • pkgsign.setDiscoveredPolicy(v)
    • pkgsign.sigPathFor(manifestPath) Signature path for a manifest path: the extension is swapped for .sig. package.lua → package.sig, programs.cfg → programs.sig.
    • pkgsign.readSig(sigPath) Read and shape-check a signature file. Returns the record or nil+err.
    • pkgsign.verifyManifest(manifestPath, body)
    • pkgsign.isVerified(verdict) True when this verdict means the bytes really came from the holder of that key (whether or not the operator has decided to trust them).
    • pkgsign.describe(verdict) Operator-facing lines for a verdict. One place, so pkg install, pkg info and the picker cannot describe the same state differently.
    • pkgsign.signManifest(manifestPath, seedRaw, opts)
    • pkgsign.normalizeLabel(label)
    • pkgsign.seedFromPassphrase(pass, label)

    kernel.power

    tos/kernel/power.lua

    • power.init(modules)
    • power.level()
    • power.energy() Get raw energy values
    • power.isActive() Is battery monitoring active?
    • power.check()
    • power.onLow(callback)
    • power.onCritical(callback)
    • power.statusString()
    • power.normalizeProfile(name) Pure: normalise an operator-typed profile name, or nil if unknown.
    • power.applyProfile(name) Apply a profile. Sets the live knobs and returns the profile name, or nil + reason for an unknown name. Does NOT persist — the caller owns /etc/tos.cfg (the kernel applies at boot, optimize power saves).
    • power.profile() The active profile name — "off" when none has been applied, because that is what the machine is then doing.
    • power.idleSeconds() Seconds between idle status-bar repaints. Read by the shell loop.
    • power.blankSeconds() Seconds of no input before the screen blanks; 0 = never.
    • power.shouldBlank(now, lastInputAt, blanked, blankSec) Pure: should a seat blank now? Split out from the event loop so the decision is testable without running a shell — the loop calls exactly this, so the test and the running system cannot disagree. blanked is the seat's current state; returns true only on the edge.
    • power.setKnob(what, seconds) Override a single knob without switching profile. Returns the value set, or nil + reason. what is "idle" or "blank".

    kernel.process

    tos/kernel/process.lua

    • proc.keyDown(charOrCode, keyboards) Is charOrCode held on any of keyboards (a list of addresses)? With no list, on any keyboard at all -- the caller could not be placed on a seat (kernel, boot, a seatless daemon), which on a one-seat machine is the same answer. A string asks by character, a number by scancode, as OpenOS's keyboard.isKeyDown does.
    • proc.withListener(pid, fn, ...)
    • proc.genOf(pid)
    • proc.spawn(name, func, opts)
    • proc.yieldCooperative()
    • proc.inProcess()
    • proc.pause(seconds) Wait seconds without taking anyone's signals: every resume is a cooperative one, so nothing is delivered to us mid-wait, and whatever arrives stays queued for this process's next ordinary yield (see the coop-resume branch in proc.tick). For short pauses in the middle of other work -- audio's gaps between tones -- where proc.sleep would swallow a keystroke. False (and no wait) outside a process; the caller decides what to do there.
    • proc.setSignalInterest(decl) Declare (or clear, with nil) the CALLING process's signal interests at runtime — same forms as opts.signalInterest at spawn. Self-service only: a process may narrow its own wakes, never another's.
    • proc.currentSession()
    • proc.currentToken()
    • proc.current()
    • proc.get(pid)
    • proc.list()
    • proc.kill(pid, opts)
    • proc.goTSR(pid)
    • proc.signal(pid, ...)
    • proc.signalKernel(pid, ...)
    • proc.setForeground(pid, displayIdx, opts)
    • proc.getForeground(displayIdx)
    • proc.cycleForeground() Cycle foreground to next live process
    • proc.preemptionAvailable() Can the scheduler actually preempt on this host? False on OpenComputers, which does not export debug.sethook (see the long note at the resume site). Exposed so doctor, ps and the tests can state the real position instead of inheriting an assumption: a caller that wants to promise "a runaway process gets killed" has to ask first.
    • proc.bgShouldResume(policy, bgSeconds, bgTicks) PURE — the background decision, split out so it can be tested without a scheduler. Returns (resume, state) where state is "drowsy"|"frozen". policy nil is handled by the caller (no policy = always resume).
    • proc.bgState(pid, now) The lifecycle state of a process, for ps and the Monitor: "live" for anything without a background policy or currently in front.
    • proc.tick(signal)
    • proc.yield()
    • proc.sleep(seconds)
    • proc.count()

    kernel.profile

    tos/kernel/profile.lua

    • profile.init(modules)
    • profile.load(session) Load the profile for session. Returns the (sanitized) profile table, plus a boolean indicating whether a profile file exists. A missing profile returns defaults() (with exists=false) so the caller can use the result unconditionally.
    • profile.save(p, session) Save a sanitized profile back to disk for session.
    • profile.apply(p, opts) Apply a loaded profile at shell start. Sets the theme override, writes env vars into the calling process, and returns the startup commands (the shell runs them itself so they're executed in the shell's command-loop context, not here).

    kernel.rc

    tos/kernel/rc.lua

    • rc.init(modules)
    • rc.runAll() Run all startup scripts in /etc/rc.d/
    • rc.stopAll() Stop all running services (called on shutdown)
    • rc.start(name) Start a specific service by name
    • rc.stop(name) Stop a specific service
    • rc.list() List all known services
    • rc.tryRestart(name) Attempt to restart a crashed service (called by supervisor tick).
    • rc.supervise()
    • rc.enable(name, content) Enable a script (create it in rc.d)
    • rc.disable(name) Disable a script (remove from rc.d)

    kernel.repair

    tos/kernel/repair.lua

    • repair.run(deps) Run the whole pass. deps = { fs, serialize, log? }. @return { fixed = N, warned = N, lines = { "...", ... } }

    kernel.sandbox

    tos/kernel/sandbox.lua

    • sandbox.reloadComponentConfig() Reload /etc/component_caps.cfg from disk. Admin command for the "I just edited the file, refresh sandbox" workflow. Existing sandboxes keep their captured cap lists; new sandbox.build()s pick up the new config.
    • sandbox.build(opts)
    • sandbox.run(src, chunkname, opts, ...)
    • sandbox.finish(env) A program has ended (however it ended): flush the last line it wrote without a newline to the runner's stdout. Runners that pass stdout call this; for any other env it does nothing.
    • sandbox.exitStatus(err) Did a program's error come from os.exit? Returns its status -- the code it passed, or true for a bare os.exit() -- or nil for a real error.
    • sandbox.describeEnd(ok, result) How a runner reports a program's end: (text, isError), or nil when there is nothing to say. os.exit() / exit(true) / exit(0) is a clean end; any other status is reported, dimly, as a status rather than as an error.

    kernel.screen

    tos/kernel/screen.lua

    • screen._pair(gpuScreens, screenAddrs)
    • screen.init() Scan for all GPU+Screen pairs and create display entries.
    • screen.count() Get the number of available displays.
    • screen.indices() Sorted list of the live STABLE seat indices. Callers that iterate seats must use this (not for i = 1, count()): indices are stable across hot-plug rebuilds, so the sequence can have holes.
    • screen.active() Get the active display info.
    • screen.get(idx) Get display by index.
    • screen.setActive(idx) Switch active display.
    • screen.rebuild() Rebuild display list (for hot-plug). Returns added, removed indices. Indices are STABLE: a surviving screen keeps its index, so removed names exactly the seats whose per-seat state (shell, session, monitor) the kernel must tear down, and every other seat's state stays valid.
    • screen.next() Cycle to next display (sparse-safe: cycles the live indices).
    • screen.gpu() Get the GPU proxy for the active display.
    • screen.getResolution() Get the resolution of the active display.
    • screen.onResized(addr, w, h)
    • screen.specFromConfig(cfg) Build a resolution spec from the system config (safe with no config).
    • screen.chooseResolution(spec, maxW, maxH, blocksW, blocksH) PURE: decide a resolution given a spec, the hardware max, and (optional) the screen's physical block dimensions. Returns appliedW, appliedH, note. note is a human-readable warning (e.g. a requested size didn't fit) or nil.
    • screen.gpuTarget(gpu, scrAddr, spec) Read a GPU's max resolution and its screen's physical block size, then choose a resolution per spec. Returns appliedW, appliedH, note.
    • screen.applyResolution(gpu, w, h) Apply a resolution to a GPU (pcall-guarded). Returns ok. Also refreshes the single-seat display.lua cache so display.clear()/getSize() don't keep using the old dimensions after a resize.
    • screen.setPolicy(spec)
    • screen.getPolicy()
    • screen.setBuffer(mode)
    • screen.invalidateAll() Tell every LIVE seat proxy that the glass changed behind it. A proxy's dirty-cell shadow is only correct while every write goes through that proxy. Anything drawing on the same GPU another way -- kernel.display directly, compat/term.lua, the login screen, a raw getGpu() handout -- moves the glass without the shadow knowing, and the proxy then ELIDES the next repaint of those cells because it believes they already hold what is being asked for. Measured on real hardware by the boot battery (70-screen-truth): after a raw write, asking the proxy to repaint a row in the colour it thought was already there left 0 of 80 columns correct. That is the reported status bar staying black. Proxies are created per call and not tracked, so this reuses the generation counter they already consult on every draw: bump it and each one invalidates itself the next time it is used. Cheap -- one integer, no bookkeeping, and it cannot leak a reference to a proxy whose seat has gone away.
    • screen.bufferMode()
    • screen.bufferStats()
    • screen.resetBufferStats()
    • screen._shadowWanted(mode, freeMem, baseNeed, reserve)
    • screen.fitDisplay(idx, spec) Fit a specific display (seat) to a spec, applying the new resolution and syncing the cached size. idx defaults to the active display; an unknown idx falls back to the active one. Returns appliedW, appliedH, note. This is the seat-aware primitive the shell uses (so resizing on seat 2 doesn't resize seat 1).
    • screen.fit(spec) Fit the ACTIVE display to a spec (or the boot policy if nil).
    • screen.restore(idx) Restore a display (default active) to the boot resolution policy, e.g. after a program that requested a custom size exits.
    • screen.list() List all displays (sorted by stable seat index; sparse-safe).
    • screen.setLabel(idx, label) Set label for a display.
    • screen.displayForKeyboard(kbAddr)
    • screen.displayForScreen(scrAddr) Look up which display index owns a screen address.
    • screen.seatDevices(idx) The raw component ADDRESSES owned by one seat. @return { gpu = addr|nil, screen = addr, keyboards = { addr, ... } } | nil
    • screen.callerSeat() The seat index that owns the CALLING process, via its scheduler principal. nil outside a process (kernel loop, boot, tests).
    • screen.callerDevices() Convenience: the seat devices of the calling process (nil if unknown).
    • screen._spanMatches(shC, shF, shB, base, x, chars, fg, bg, W)
    • screen._diffWindow(shC, shF, shB, base, x, chars, fg, bg, W)
    • screen._fillWindow(shC, shF, shB, x1, y1, x2, y2, ch, fg, bg, W)
    • screen.displayProxy(idx) Create a display proxy for index idx: a table that looks like the kernel.display API but draws to a specific GPU/screen pair. Core drawing methods (set/fill/clear) are implemented directly for performance; higher-level TUI methods delegate to display.withContext.

    kernel.securefs

    tos/kernel/securefs.lua

    • securefs.init(modules)
    • securefs.exists(path, session)
    • securefs.isDirectory(path, session)
    • securefs.list(path, session)
    • securefs.readFile(path, session)
    • securefs.size(path, session)
    • securefs.lastModified(path, session)
    • securefs.writeFile(path, content, session)
    • securefs.writeFileAtomic(path, content, session) writeFile with kernel.fs's temp-then-rename guarantee: a power cut or a refused write leaves the old file or the new one, never a truncated one. For per-user state that is costly to lose (the keychain vault). The staged <path>.tos-tmp sits in the same directory and users.decide applies the same rule to it, so checking path covers both. Kernel-only: not on the forSession proxy handed to sandboxed code.
    • securefs.appendFile(path, content, session)
    • securefs.makeDirectory(path, session)
    • securefs.setOperatorOverride(session, enabled)
    • securefs.operatorOverride(session) Is the override armed for this session? (For UI to show the state.)
    • securefs.removeRefusal(path, session)
    • securefs.remove(path, session)
    • securefs.rename(from, to, session)
    • securefs.copy(src, dst, session)
    • securefs.open(path, mode, session)
    • securefs.normalize(path)
    • securefs.split(path)
    • securefs.join(...)
    • securefs.spaceTotal(path)
    • securefs.spaceUsed(path)
    • securefs.spaceFree(path)
    • securefs.mounts()
    • securefs.mount(path, proxy, session) — admin
    • securefs.unmount(path, session) — admin
    • securefs.home(session) Get the current user's home directory
    • securefs.resolve(path, session) Resolve ~ to home directory
    • securefs.forSession(session)
    • securefs.opaqueMountId(mountPoint, addr) A stable stand-in for a disk's component address, for callers that may not see the real one: reproducible across calls (OpenOS code keys caches on address) and never the address itself.

    kernel.selftest

    tos/kernel/selftest.lua

    • selftest.init(deps)
    • selftest.markerPaths(fsMod) Every place a marker may live, in priority order. Markers carry two different facts, and only one of them may come from a disk: WHETHER the battery runs /etc/selftest.on, on the machine itself WHAT it runs, and how the test disk: its checks, and a selftest.on of its own for the options
    • selftest.diskMarkers(fsMod) The disk-side markers present right now. They pick options and mark a disk root as a folder of checks; they do not arm anything (enabled).
    • selftest.enabled(fsMod)
    • selftest.activeMarker(fsMod) Which marker armed it, or nil. Only ever the machine's own.
    • selftest.readMarker(fsMod) Marker contents are optional config, parsed leniently: a machine that boots the battery must not fail to boot because the marker had a typo in it. shutdown=true power off when the run finishes (for CI) only= run only checks whose name starts with this screen=true allow checks that DRAW on the boot console screen is off by default because the console is a scrolling log and the battery logs while it runs, so a drawing check cannot win: a row saved at one moment shows different content two seconds later, and restoring it puts stale text back. Save/restore cannot beat a race against the log. Turning the screen checks on is choosing to accept a messy console for the round, which is a fair trade when they are the ones answering the question. Every marker present contributes. For each option the first marker in priority order that sets it wins, so the usual EMPTY /etc/selftest.on leaves a test disk's shutdown=true in force, and the machine's own marker can still override the disk.
    • selftest.discover(fsMod) Check files live on a TEST DISK, not in the base image: the runner is small enough to ship dormant, but a battery of checks is not. Any mounted /mnt//selftest/ is searched, so on an ARMED machine inserting the disk is the whole install step (see enabled). The one exception is a selftest/ holding a package.lua: that is the selftest package on an Optional Utilities disk, not a test disk's checks.
    • selftest.run(opts)

    kernel.serialize

    tos/kernel/serialize.lua

    • serialize.encode(tbl)
    • serialize.compact(tbl)
    • serialize.decode(str, opts)
    • serialize.loadFile(fs, path)
    • serialize.saveFile(fs, path, tbl)

    kernel.sha256

    tos/kernel/sha256.lua

    • sha256.hex(msg) SHA-256 of a string → 64-char lowercase hex.

    kernel.sha512

    tos/kernel/sha512.lua

    • sha512.hex(msg) SHA-512 of a string → 128-char lowercase hex.
    • sha512.raw(msg) SHA-512 of a string → 64 RAW bytes. Ed25519 wants the bytes, not the hex; going through hex and back would double the allocation on a box where that is the scarce resource.

    kernel.srm

    tos/kernel/srm.lua

    • srm.deps(d) Fill in whatever the caller didn't inject. Never mutates the argument.
    • srm.worst(rep) Worst severity in a report, as a single word. Used for exit summaries.
    • srm.parseFault(data) Extract a parked fault code from raw EEPROM data. nil if none.
    • srm.stripFault(data) Remove the SRM line, preserving every other line verbatim. Losing the boot address here would leave the machine prompting "Boot drive changed" on every power-on, so this is deliberately surgical.
    • srm.readFault(deps) Read the parked code, if any. Returns { code = "K4", why = "..." } or nil.
    • srm.clearFault(deps) Clear the parked code once it has been reported. Returns ok, err.
    • srm.criticalPaths(deps) The boot-critical file list, plus the source it came from. @return paths(array), source(string)
    • srm.storePathFor(path) Where a path's stored copy lives.
    • srm.loadIndex(deps) Read the baseline index, or nil if there isn't one.
    • srm.baseline(deps, opts) Capture a known-good baseline. "Known-good" is an honest claim about WHEN, not a proof of purity: SRM cannot know a file was pristine, only that this is what it looked like at the moment you declared the system good (fresh install, or a clean verify). From then on drift is detectable, and a stored copy's own hash proves the COPY hasn't rotted. That is the same guarantee DISM's local source gives, stated plainly. opts.content also store a copy of each file (enables local repair) opts.paths override the file set (defaults to the critical set) @return true, summary | false, err
    • srm.scan(deps, opts) Compare the live system against the baseline. Findings, in the order an operator cares about them: err a critical file is MISSING err a critical file's content DRIFTED from the baseline warn a stored copy has itself rotted (the repair source is bad) warn a critical file has no baseline entry (added since capture) @return report (plus report.drift / report.missing arrays for repair)
    • srm.restore(deps, opts) Restore files from a verified source. #SEC — a restore writes over system files, so SRM will only do it from a copy whose hash matches the baseline. That applies to BOTH sources: the local store and an external mount. Without a baseline there is nothing to check a candidate against, so the operation refuses unless the caller passes unverified=true — the same explicit-consent shape pkg install --allow-unverified already uses for running unchecked code. opts.paths which files (defaults to scan's missing+drift) opts.source external source root, e.g. "/mnt/x" (install media) opts.unverified accept a source copy that cannot be hash-checked @return report
    • srm.storeUsage(deps) Disk cost of the store, so srm status can answer "what is this costing me?" without the operator running du.
    • srm.repair(deps, opts) Run the maintenance pass. Stage 1 is kernel.repair: the mechanically-safe fixes (interrupted atomic writes, orphaned temps, a corrupt boot.cfg, stale /var/run, oversized logs). It is unchanged and still runs on its own at boot — SRM calls the same module rather than reimplementing it. Stage 2 is new and only possible because of the baseline: put back critical files that went missing or changed, from a verified source. It is OPT-IN (opts.restore) because overwriting system files is not a "mechanically safe" fix in the sense stage 1 means — it is a decision.
    • srm.parseArgs(args) Parse an srm argument list into (verb, flags, paths). Lives here rather than in the shell command so it is pure and testable: the grammar has one genuinely fiddly rule, and getting it wrong turns a mount point into a file to overwrite. * the first non-flag word is the VERB (defaults to "status") * every later non-flag word is a PATH * --flag is a boolean; --flag=value carries a value * --source <mount> also accepts its value as the next word, and CONSUMES it BY INDEX. Matching it by value afterwards only worked when the mount happened to be the first path, so srm restore /tos/kernel/fs.lua --source /mnt/x tried to restore a file called /mnt/x. A --source with nothing to consume comes back as boolean true, which the caller reports as a usage error rather than silently ignoring.
    • srm.status(deps, opts) What SRM knows right now, cheaply: the parked POST fault, whether a baseline exists and how stale it is, what the store costs, and whether the last shutdown was clean. Deliberately does NOT hash anything — this is the command an operator runs first, and it must be instant. opts.clearFault consume the parked code (boot path does; the shell leaves it so the operator can look twice)

    kernel.swap

    tos/kernel/swap.lua

    • swap.init(modules)
    • swap.clear() Wipe all swapped data and any orphan files under /var/swap.
    • swap.store(key, value) Spill a value to disk under key, freeing the caller to drop its RAM reference. Returns (true) or (false, err). Storing nil frees the key. @param key string @param value any (serializable: string/number/boolean/table; functions and userdata are not serializable and are dropped by the encoder)
    • swap.fetch(key) Page a value back into RAM. Returns the value, or nil if not present (or unreadable / undecodable — swap is scratch, so we fail soft).
    • swap.free(key) Drop a key's on-disk copy. Returns true if it existed.
    • swap.has(key)
    • swap.keys()
    • swap.usage() { bytes = used, count = entries, max = cap } — for status UIs.
    • swap.table(opts) Return a disk-backed table. Entries live in swap; a small in-RAM LRU cache (opts.hot, default 16) keeps the working set fast. Writes are write-through (durable in swap immediately), so an LRU eviction only drops the cache copy. Honors #, pairs(), and nil-assignment delete. Free it with swap.freeTable(proxy) when done.
    • swap.freeTable(proxy) Release every swap entry owned by a proxy from swap.table().

    kernel.sysinfo

    tos/kernel/sysinfo.lua

    • sysinfo.stickTier(perStickKB) Tier name for a SINGLE stick of perStickKB, or nil if not a stock size.
    • sysinfo.ramSummary(totalKB, modules, sticks) Human summary of installed memory: "2x T3.5", "T3.5 + T2.5", or an honest capacity when the sticks can't be enumerated / aren't stock sizes. @param sticks table|nil per-stick KB; nil asks hal to probe the hardware
    • sysinfo.benchmarkCallBudget(computer) Opt-in behavioral CPU estimate. NOT run at boot (it burns ~one game tick). Counts how many cheap direct calls complete before the tick advances — a rough proxy for the CPU's per-tick call budget, which scales with tier. Returns (calls, tierGuess). The operator can still override; this is a "best guess" for the boot-settings "detect" action when getDeviceInfo can't name the tier.
    • sysinfo.gather(deps, overrides, onProgress)
    • sysinfo.rows(inv)
    • sysinfo.diskRole(d, isBoot, state) The honest role name for one drive row. The old rule collapsed everything small into "RAM Disk", which mislabeled a real 512KB floppy (the Optional Utilities disk!) and hid what tmpfs actually is. Now: the boot disk, OC's built-in scratch fs (named by its mount point, /tmp), AMIBIOS-style lettered floppies, and numbered data drives. state carries the running counters across a listing.
    • sysinfo.render(inv, ctx)

    kernel.theme

    tos/kernel/theme.lua

    • theme.init(modules)
    • theme.list()
    • theme.preset(name)
    • theme.describe(name)
    • theme.current()
    • theme.isOverridable(key)
    • theme.overridableKeys()
    • theme.apply(name, overrides)
    • theme.setColor(key, value)
    • theme.resetOverrides()
    • theme.hasSavedTheme(session)
    • theme.saveForUser(session)
    • theme.applyForUser(session)
    • theme.clearForUser(session)

    kernel.trash

    tos/kernel/trash.lua

    • trash.init(modules)
    • trash.put(srcPath, session) Move a file (or directory) into the caller's trash. Returns (true, trashEntryPath) on success, (false, reason) otherwise. The session arg drives both the trash location and the ACL check on the source path.
    • trash.list(session) List the caller's trash entries. Returns an array of { name, path, size, origin, deletedAt } records sorted newest-first.
    • trash.restore(name, opts) Restore a trash entry by its name back to its original path (or to destOverride if supplied). Returns (true) on success, (false, reason) otherwise. Refuses to overwrite an existing file unless the caller passes force = true in opts.
    • trash.empty(session) Permanently empty the caller's trash.
    • trash.usage(session) Cap helpers (for diag reporting).

    kernel.users

    tos/kernel/users.lua

    • users.setElevation(session, password, cap) Configure (or replace) the elevation password. ROOT only. cap is the ceiling elevation grants: TIER.ADMIN or TIER.ROOT.
    • users.clearElevation(session) Disable elevation entirely (remove the password). ROOT only.
    • users.elevationInfo() Public status (no secret): is elevation configured, and its ceiling.
    • users.elevate(session, password) Elevate session using the elevation password. Returns a NEW elevated session table (same user + home, tier raised to min(cap, ROOT)), flagged elevated, or (nil, reason). The caller decides whether to use it transiently (one command) or register it as a token (sudo -s). Guests are refused outright; a dummy verify still runs so a wrong password and a disabled/guest case are timing-indistinguishable.
    • users.registerSession(sess) Register an elevated session as a real token (for sudo -s). Returns the token, or nil. Kept separate so per-command elevation stays token-free (nothing to leak / forget to revoke).
    • users.init(modules)
    • users.createFirstBoot() First-boot setup: create root with default password
    • users.createGuestAccount() Create the built-in guest account (no password, GUEST tier)
    • users.guestEnabled() Check if guest access is currently enabled
    • users.guestLogin()
    • users.create(creator, username, password, tier)
    • users.delete(actor, username) Delete a user account
    • users.changePassword(actor, username, oldPassword, newPassword) Change a user's password @return true[, note] on success; note explains a keychain that could not follow the new password. (false, err) on failure.
    • users.setTier(actor, username, newTier) Set a user's access tier
    • users.setLocked(actor, username, locked) Lock/unlock a user account
    • users.login(username, password, opts)
    • users.promoteAfterFirstBoot(token)
    • users.logout(token) Logout / destroy session
    • users.currentSession()
    • users.getSession(token) Get session by token (checks expiry)
    • users.sweepSessions() Sweep all expired sessions (call periodically from cron or kernel tick)
    • users.kernelSession()
    • users.loginSession(dIdx)
    • users.sessionFor(username)
    • users.createSession(userOrName)
    • users.canAccessAs(session, path, mode)
    • users.canAccess(path, mode) Check if current user can access a path (back-compat wrapper)
    • users.list(forTier)
    • users.getUser(username) Get user info (returns a shallow copy to protect internal state)
    • users.exists(username) Check if user exists

    kernel.ustr

    tos/kernel/ustr.lua

    • ustr.len(s) Number of CHARACTERS in s (not bytes).
    • ustr.width(s) Display WIDTH of s in screen columns (wide CJK cells count as 2).
    • ustr.sub(s, i, j) Character-indexed substring (like string.sub but per character).
    • ustr.fit(s, cols) Truncate s so it renders in at most cols display columns. Never cuts a multi-byte character in half.
    • ustr.padR(s, cols) Pad-right / pad-left to an exact column width (fits first).
    • ustr.padL(s, cols)
    • ustr.centerOffset(s, cols) Starting column that centres s inside cols columns (1-based offset 0).

    kernel.vault

    tos/kernel/vault.lua

    • vault.encrypt(plaintext, passphrase, opts) Encrypt plaintext under passphrase. Returns the encoded blob (header + ciphertext) plus a metadata table for the caller to log.
    • vault.decrypt(blob, passphrase) Decrypt a vault blob. Returns (plaintext, info) on success or (nil, err) on failure. Returns a specific "MAC mismatch" error when the integrity check fails — distinguishes wrong-passphrase from tampered-data for the caller's UI.
    • vault.isEncrypted(s) Heuristic check: is this string already a vault blob? Accepts either wire version (#SEC CR-7).