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.
kernel.audio(17)kernel.backup(4)kernel.bootcfg(8)kernel.bootsettings(5)kernel.bootsteps(4)kernel.clipboard(9)kernel.compress(5)kernel.config(9)kernel.cron(7)kernel.crypto(13)kernel.datacard(3)kernel.diag(1)kernel.diskmount(5)kernel.display(28)kernel.ed25519(3)kernel.env(6)kernel.errors(9)kernel.event(13)kernel.fs(27)kernel.hal(17)kernel.i18n(9)kernel(29)kernel.internet(9)kernel.jbod(3)kernel.keychain(9)kernel.log(16)kernel.logo(1)kernel.monitor(11)kernel.net.aliases(9)kernel.net.chatpair(7)kernel.net(39)kernel.net.mesh(15)kernel.net.meshctl(6)kernel.net.protocol(14)kernel.net.remote(6)kernel.net.transfer(5)kernel.net.trust(22)kernel.netfs(14)kernel.netrepair(8)kernel.notify(10)kernel.pipe(2)kernel.pkg(30)kernel.pkgops(42)kernel.pkgremote(12)kernel.pkgsign(19)kernel.power(15)kernel.process(27)kernel.profile(4)kernel.rc(10)kernel.repair(1)kernel.sandbox(6)kernel.screen(38)kernel.securefs(31)kernel.selftest(8)kernel.serialize(5)kernel.sha256(1)kernel.sha512(2)kernel.srm(16)kernel.swap(10)kernel.sysinfo(7)kernel.theme(14)kernel.trash(6)kernel.users(30)kernel.ustr(7)kernel.vault(3)
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.
tos/kernel/backup.lua
backup.init(modules)backup.snapshot(srcRoot, destPath, opts)Create a snapshot. Writes the encoded blob todestPath. @return (true, summary) or (false, err)backup.inspect(srcPath, opts)Inspect a backup file. Returns (info, nil) on success, whereinfois { 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 intodestRoot(defaults to the original root recorded in the header). Returns (true, summary) or (false, err).
tos/kernel/bootcfg.lua
bootcfg.echoMinLevel(verbosity)bootcfg.load(fs)Load /etc/boot.cfg, fail-safe. Never raises; an unreadable/corrupt file yields thenormaldefaults 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/writeFileAtomicbootcfg.verbosity(cfg)Effective muter level for this config (explicit override, else profile).bootcfg.shows(cfg, level)True iflevelshould 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 featurenameload? 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.detectedis 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 nototalKB(off-box) auto is the panels, as it always was.
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.probeis injectable so the model stays testable without hardware.bootsettings.fields(cfg, ramLabel)Return the editable fields with their current display values forcfg. @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 ofcfgbydir(+1/-1) through its value ring. Mutates and returnscfg. Out-of-range idx is a no-op.bootsettings.cycleKey(cfg, key, dir)Advance the field whose key iskeybydir. 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)
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.
tos/kernel/clipboard.lua
clipboard.set(value, seat)Put text on the seat's clipboard.valueis 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).
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 ifblobis a TCZ container produced by pack().compress.pack(data)Compressdatainto 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, packedBytescompress.unpack(blob)Reverse pack(). Returns the original data, or (nil, err). A "stored" blob decodes without a data card; a "compressed" blob needs one.
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 modeconfig.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 nameconfig.reset(key)Reset a key to default
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
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 keycrypto.decrypt(data, key, method)crypto.checksum(data)crypto.sign(data, secret)
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 nodatacomponent exists.
tos/kernel/diag.lua
diag.run(report, opts)Run every check, callingreport(line, severity)for each line. Returns an aggregate { ok = N, info = N, warn = N, err = N }.
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 diskaddr(its filesystemproxy) 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, reasondiskmount.unmount(addr)Unmount the disk ataddr, if it was mounted here. @return mountPoint, label | nildiskmount.mountPointOf(addr)Where diskaddris mounted, and the label it was mounted under.
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 (thescreen rescommand / 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 linedisplay.box(x, y, w, h, title, style)Draw a box with single-line borderdisplay.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 widthdisplay.writeWrapped(x, y, w, maxH, text, fg, bg)Write text within a region, word-wrappingdisplay.dialog(title, message, buttons)Show a centered message dialogdisplay.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.
tos/kernel/ed25519.lua
ed25519.publickey(seed)Public key (32 bytes) for a 32-byte secret seed.ed25519.sign(msg, seed, pk)Signmsgwith a 32-byte secret seed. Returns a 64-byte signature.ed25519.verify(msg, sig, pk)
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).
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 -- sowhy lsstill 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.
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 + idevent.removeSource(source)Remove all listeners registered by a sourceevent.purgeOwner(pid)event.timer(delay, callback, source)event.interval(delay, callback, source)Schedule a repeating callbackevent.cancelTimer(id)Cancel a timerevent.pull(timeout)event.pullFiltered(filter, timeout)Pull a specific signal type, with timeoutevent.reservedSignal(name)event.push(...)
tos/kernel/fs.lua
fs.init(bfs)fs.normalize(path)fs.split(path)Split path into directory and filenamefs.join(...)Join path segmentsfs.mount(path, proxy)fs.unmount(path)Unmountfs.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 stringfs.writeFile(path, content)Write string to file (overwrite)fs.appendFile(path, content)Append to filefs.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()
tos/kernel/hal.lua
hal.stickTier(kb)Tier name for a SINGLE stick ofkb, 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 callhal.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 clearsneedis 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 noneedto 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()
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.defaultis 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 }. Powerslang dump.
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);doctorreports 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()
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 theinternetcapability is the whole of the gate.
tos/kernel/jbod.lua
jbod.makePool(members)jbod.loadConfig(fsModule)jbod.saveConfig(fsModule, cfg)
tos/kernel/keychain.lua
keychain.init(modules)keychain.unlock(masterPass, session)Unlock the keychain for the current session.masterPassis typically the user's login password. Returns (true) on success, (false, err) otherwise.keychain.lock(session)Clear the unlocked map forsession.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.nameis a short identifier ("home-tape", "work-floppy").passphraseis 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)
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 (forlog filterUI).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)
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 }.
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'sneeds, 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", whileservicesaid 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)
tos/kernel/net/aliases.lua
aliases.init(modules)aliases._load()aliases.set(alias, address)— admin Registeralias→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.
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 withpeerusing 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.
tos/kernel/net/init.lua
net.portOwner(port)Whose portportis 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'shostname <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 + IDnet.onceFrom(msgType, addr, callback)Register a one-shot listener that fires ONLY formsgTypepackets received fromaddr. 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 internalfiredflag 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. Useforce = trueto bypass the positive cache.net.invalidateVerification(addr)Invalidate the challenge-response cache for one peer (or all peers ifaddris 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 machinesnet.hello(address)Send hello to a known peer (exchange hostnames)net.sendMessage(address, text)Send an encrypted message to a trusted peernet.requestTrust(address)Request trust from a remote machinenet.getTrust()Get the trust manager instancenet._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 fortimeoutseconds, return the peers found in this scan.net.getProtocol()Get the protocol modulenet.status()Get network status summarynet.shutdown()mesh.newEnvelope(from, to, fields)Build a fresh outbound envelope.idMUST 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)Recordid; 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 ifaddralready appears in the envelope's traversed path.mesh.route(env, selfAddr, isForMe, seen)Decide what a node atselfAddrshould 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).seenis 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-broadcastout(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.nowstamps 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 mostkeepremain. Returns how many went. They are a courtesy, so memory pressure takes them first.mesh.dueItems(ob, now)The outbox entries due at timenow, their schedules advanced; any past their deadline are dropped. Returns (items, droppedCount).mesh.due(ob, now)Return the envelopes due for (re-)flooding at timenow, 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 /mailUI).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)protocol.makePacket(msgType, payload, opts)protocol.serialize(packet)Serialize a packet to a string for transmissionprotocol.deserialize(str)protocol.validate(packet)Check if a deserialized table is a valid TOS packetprotocol.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)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)transfer.setEnabled(v)transfer.isEnabled()transfer.init(modules)transfer.request(address, remotePath, localPath, opts)transfer.handleRequest(packet, fromAddr)trust.init(modules)trust.getLevel(address)trust.getPeer(address)Get peer infotrust.isAllowed(address, msgType)Check if a specific message type is allowed from an addresstrust.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 leveltrust.trustFull(actor, address, actorTier)Convenience: trust a peer at TRUSTED leveltrust.block(actor, address, actorTier)Block a peertrust.revoke(actor, address, actorTier)Revoke trust (reset to UNKNOWN)trust.forget(actor, address, actorTier)Remove a peer entirely from the databasetrust.setSecret(actor, address, secret, actorTier)— admintrust.getSecret(actor, address, actorTier)— admintrust.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 requeststrust.clearPendingRequest(address)Clear a pending requesttrust.listPeers()List all known peerstrust.stats()Count peers by trust leveltrust.levelName(level)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)Isaddrallowed 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 membernetrepair.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, whynetrepair.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 }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 thanafterSeqthat are still live. Pure w.r.t. the caller: it does NOT consume anything, so every seat sees each notice. @return list, highSeqnotify.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, whynotify.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 keepsresultsfrom growing without bound on a long uptime.notify.depth()How many notices are waiting (fordoctor/ the Monitor).notify._reset()Test/reset hook: forget everything.pipe.create()pipe.parse(input)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)— adminpkg.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 rejectedinternet, 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)Isfacetpermitted for packagepkgName? 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 refusalpkg.capsForFile(path)The caps a program FILE that a package installed runs with, and that package's name -- or nil when no enabled package ownspath.pkg.ownerOfFile(path)Which installed package shipspath, enabled or not -- or nil.pkg.noteServiceStarted(svcName, opts)— adminservice 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 providesname, 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 providesnamedeclare it a FULL-SCREEN program (manifestfullscreen = 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 alookupfunctionlookup(name) -> manifestthat knows about both already-installed packages and packages-available-to-install. Returns (order, err) whereorderis 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.pkg.foreignDefaultCaps()Capabilities a foreign package gets when its manifest declares none. Pure; exposed for the tests.pkg.findConflicts(m, ignoreName)Conflicts betweenmand 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 namepkg.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, errpkg.forgetModules(paths)Drop from the module cache every module the kernel loader read from one ofpaths(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 afterpkg upgraderan 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 realcomputerbe 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)— adminpkg.runtimeRefusal(m)pkg.describeContradictions(list)Contradictions as one line for a refusal.pkg.plan(repoDir, targetName, opts)Work out an install oftargetNamewithout 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 metpkg.installWithDeps(repoDir, targetName, opts)— adminpkg.uninstall(name, opts)— adminpkg.repoRoots()Every directorypkgwill 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 aconfirm(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), sopkg 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, errpkg.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 frompkg repo list, a URL, or "all".pkg.trustList()Trusted publisher keys: { {label=, key=, fingerprint=}, ... }pkg.trustAdd(label, pubHex, opts)— adminpkg.trustRemove(label, opts)— adminpkg.trustRequire(on, opts)— adminpkg.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)— adminpkg.checkSignature(srcDir)Verify (without installing) whatever is insrcDir. This is thepkg 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)— adminpkg.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)— adminpkg.searchRemote(opts)Everything available across the configured repos.pkg.installRemote(name, opts)— adminpkgremote.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'srepoverb 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 reposfrom(a package name) names. A configured repo is not recorded -- it is already more than known -- and a URL that is already known gainsfrom. Returns how many were new.pkgremote.forget(which)Forget learned repos: "all", the numberpkg repo listshows, 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)Downloadnamefrom 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.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)TrustpubHexunderlabel. 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, sopkg install,pkg infoand the picker cannot describe the same state differently.pkgsign.signManifest(manifestPath, seedRaw, opts)pkgsign.normalizeLabel(label)pkgsign.seedFromPassphrase(pass, label)power.init(modules)power.level()power.energy()Get raw energy valuespower.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 powersaves).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.blankedis 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.whatis "idle" or "blank".proc.keyDown(charOrCode, keyboards)IscharOrCodeheld on any ofkeyboards(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)Waitsecondswithout 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 processproc.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 sodoctor,psand 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".policynil is handled by the caller (no policy = always resume).proc.bgState(pid, now)The lifecycle state of a process, forpsand the Monitor: "live" for anything without a background policy or currently in front.proc.tick(signal)proc.yield()proc.sleep(seconds)proc.count()profile.init(modules)profile.load(session)Load the profile forsession. Returns the (sanitized) profile table, plus a boolean indicating whether a profile file exists. A missing profile returns defaults() (withexists=false) so the caller can use the result unconditionally.profile.save(p, session)Save a sanitized profile back to disk forsession.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).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 namerc.stop(name)Stop a specific servicerc.list()List all known servicesrc.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)repair.run(deps)Run the whole pass. deps = { fs, serialize, log? }. @return { fixed = N, warned = N, lines = { "...", ... } }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 passstdoutcall 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.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 (notfor 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, soremovednames 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.noteis 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 perspec. 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.idxdefaults 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, ... } } | nilscreen.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 indexidx: 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.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-tmpsits in the same directory and users.decide applies the same rule to it, so checkingpathcovers 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)— adminsecurefs.unmount(path, session)— adminsecurefs.home(session)Get the current user's home directorysecurefs.resolve(path, session)Resolve ~ to home directorysecurefs.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 onaddress) and never the address itself.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 optionsselftest.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 consolescreenis 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'sshutdown=truein 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)serialize.encode(tbl)serialize.compact(tbl)serialize.decode(str, opts)serialize.loadFile(fs, path)serialize.saveFile(fs, path, tbl)sha256.hex(msg)SHA-256 of a string → 64-char lowercase hex.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.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 cleanverify). 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, errsrm.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 shapepkg install --allow-unverifiedalready 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 reportsrm.storeUsage(deps)Disk cost of the store, sosrm statuscan 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 ansrmargument 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 *--flagis a boolean;--flag=valuecarries 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, sosrm restore /tos/kernel/fs.lua --source /mnt/xtried to restore a file called /mnt/x. A--sourcewith 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)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 underkey, 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().sysinfo.stickTier(perStickKB)Tier name for a SINGLE stick ofperStickKB, 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 hardwaresysinfo.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.statecarries the running counters across a listing.sysinfo.render(inv, ctx)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)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 todestOverrideif supplied). Returns (true) on success, (false, reason) otherwise. Refuses to overwrite an existing file unless the caller passesforce = truein opts.trash.empty(session)Permanently empty the caller's trash.trash.usage(session)Cap helpers (fordiagreporting).users.setElevation(session, password, cap)Configure (or replace) the elevation password. ROOT only.capis 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)Elevatesessionusing the elevation password. Returns a NEW elevated session table (same user + home, tier raised to min(cap, ROOT)), flaggedelevated, 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 (forsudo -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 passwordusers.createGuestAccount()Create the built-in guest account (no password, GUEST tier)users.guestEnabled()Check if guest access is currently enabledusers.guestLogin()users.create(creator, username, password, tier)users.delete(actor, username)Delete a user accountusers.changePassword(actor, username, oldPassword, newPassword)Change a user's password @return true[, note] on success;noteexplains a keychain that could not follow the new password. (false, err) on failure.users.setTier(actor, username, newTier)Set a user's access tierusers.setLocked(actor, username, locked)Lock/unlock a user accountusers.login(username, password, opts)users.promoteAfterFirstBoot(token)users.logout(token)Logout / destroy sessionusers.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 existsustr.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 mostcolsdisplay 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 insidecolscolumns (1-based offset 0).vault.encrypt(plaintext, passphrase, opts)Encryptplaintextunderpassphrase. 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).
tos/kernel/net/mesh.lua
tos/kernel/net/meshctl.lua
tos/kernel/net/protocol.lua
tos/kernel/net/remote.lua
tos/kernel/net/transfer.lua
tos/kernel/net/trust.lua
tos/kernel/netfs.lua
tos/kernel/netrepair.lua
tos/kernel/notify.lua
tos/kernel/pipe.lua
tos/kernel/pkg.lua
tos/kernel/pkgops.lua
tos/kernel/pkgremote.lua
tos/kernel/pkgsign.lua
tos/kernel/power.lua
tos/kernel/process.lua
tos/kernel/profile.lua
tos/kernel/rc.lua
tos/kernel/repair.lua
tos/kernel/sandbox.lua
tos/kernel/screen.lua
tos/kernel/securefs.lua
tos/kernel/selftest.lua
tos/kernel/serialize.lua
tos/kernel/sha256.lua
tos/kernel/sha512.lua
tos/kernel/srm.lua
tos/kernel/swap.lua
tos/kernel/sysinfo.lua
tos/kernel/theme.lua
tos/kernel/trash.lua
tos/kernel/users.lua
tos/kernel/ustr.lua
tos/kernel/vault.lua