
This document explains the kernel architecture, the boot lifecycle, how to create programs, jobs, libraries, and subsystems, how the message system works, database persistence, and how to develop and run locally.
Contents:
- Overview and Conventions
- Kernel Boot and Lifecycle
- Core (System) and Managers
- Jobs and Subsystems
- Libraries and Program/Object Catalog
- Message System (Message Queue)
- Persistence (DatabaseManager / SQLite WASM / IndexedDB)
- Security and Authority (AuthorityManager)
- Base Program class and Terminal APIs
- Creating a new Program (step by step)
- System Auditing (AUDITJRNP, DSPAUDLOG, DSPAUDMON)
- How to run in development
- Tips and Troubleshooting
WebOS/400 is a web OS inspired by IBM i/AS/400 with a 100% object‑oriented architecture. Every system element (programs, jobs, subsystems, libraries) is an object derived from SystemObject
.
Key directories:
src/kernel/core/
: kernel core and managers (jobs, subsystems, libraries, messages, authority, DB, etc.).src/apps/system/
: system programs (e.g., SIGNON, MAIN, SYSSTATUS, commands and utilities).src/ui/
: UI (Terminal, SystemUI, styles, etc.).
Conventions and IDs:
- Objects use IDs in the form
LIB/NAME.TYPE
(seeSystemObject
). - Qualified program name:
LIB/PGM
; unqualified:PGM
resolved via job’s current library and library list. - System message queues:
QSYSOPR
,QSYSMSG
,QHIST
.
File: src/kernel/bootstrap.js
Boot flow:
- Instantiate
System
and callsystem.initialize()
. - Expose
window.webOS.system = system
for console inspection. - Create and initialize
SystemUI
andTerminal
; attach Terminal to#terminal-container
. - Start the interactive subsystem
QINTER
. - Create the initial user job:
QSECOFR/QINTER/SIGNON
. - Run the
SIGNON
program on the initial job throughProgramManager
.
Boot failures display an error screen with F3/F12 hints.
File: src/kernel/core/System.js
Kernel components:
MessageQueue
: message queues and listeners.JobManager
: create/end/list jobs.SubsystemManager
: create and control subsystems.LibraryManager
: libraries and objects.AuthorityManager
: users, groups, authority checks.ProgramManager
: program registration, cataloging, and execution.DatabaseManager
: SQLite persistence (sql.js/WASM) in IndexedDB.
During System.initialize()
:
- Initializes DB, MQ, and all managers.
- Ensures system libraries and subsystems (QSYS, QGPL, QTEMP, etc.; QINTER, QBATCH, QCMN).
- Sends a “system initialized” message to
QSYSOPR
. - Starts
QBATCH
and launches the audit jobAUDITJRNP
in the background (non‑blocking).
System status:
getSystemStatus()
exposes state, uptime, simulated load, job/subsystem counters, and memory.
Files: src/kernel/core/JobManager.js
, src/kernel/core/Job.js
, src/kernel/core/SubsystemManager.js
, src/kernel/core/Subsystem.js
Jobs:
- ID format:
NNNNNN/USER/JOBNAME
(e.g.,000001/QSECOFR/SIGNON
). - Each job has its own message queue (
JOB_<id>
), status, priority, and call stack. - Libraries:
currentLibrary
andlibraryList
with helpers (setCurrentLibrary
,setLibraryList
,addToLibraryList
,getLibraryList
). Defaults:currentLibrary='QGPL'
,libraryList=['QTEMP','QGPL','QSYS']
. - Job start/end events are sent to
QSYSMSG
(for auditing).
Subsystems:
- Config:
description
,maxJobs
,autoStart
,priority
. - Each subsystem has a
SBS_<name>
queue and a state (INACTIVE/ACTIVE). SubsystemManager
handles create/start/stop;System.initialize()
createsQINTER
,QBATCH
,QCMN
(autoStart=true).
Files: src/kernel/core/LibraryManager.js
, src/kernel/core/Library.js
, src/kernel/core/ProgramManager.js
LibraryManager
loads/creates libraries and persists them in DB (libraries
).- Each
Library
keeps an object map (addObject
,getObject
,listObjects
). ProgramManager
registers in‑memory programs and catalogs them in DB (programs
).
Program resolution in ProgramManager.runProgram()
:
- Accepts
LIB/PGM
(qualified) orPGM
(unqualified). - For unqualified names: searches the job’s current library and *LIBL (see
Job
helpers). - Verifies the DB catalog; requires the program to be loaded in
ProgramManager
and cataloged. - Performs execution authority checks via
AuthorityManager
.
Registration and cataloging:
- System programs are imported and registered in
registerSystemPrograms()
. registerProgram(name, programClass, library)
enables dynamic registration (adds to a library and catalogs in DB, persisted).
File: src/kernel/core/MessageQueue.js
- Default queues:
QSYSOPR
,QSYSMSG
,QHIST
(created at init). sendMessage(queue, msg)
: assignsid
,timestamp
, enqueues, logs to history, notifies listeners.receiveMessage(queue, timeout)
: single‑consumer FIFO.addListener(queue, fn)
: every listener receives a broadcast fanout of each message.- Additional queues are created on demand.
Usage patterns:
- Jobs and subsystems emit events to
QSYSMSG
(e.g.,JOB_STARTED
,SUBSYSTEM_STOPPED
). - Programs may consume/produce messages on their own or system queues.
File: src/kernel/core/DatabaseManager.js
- Uses
sql.js
(WASM) loaded via CDN and persists the DB binary in IndexedDB. - DB name:
webos400
(object storefiles
), file key:webos400.db
. - Migrations create tables:
users
,libraries
,files
,members
,records
,programs
,audit_log
. - Indexes: unique
idx_audit_log_event_id
guarantees event de‑duplication. - Helpers:
execute
,query
,queryOne
,persist
.
Note: for existing DBs, light migrations are applied during initialization to ensure new tables and indexes.
File: src/kernel/core/AuthorityManager.js
- Loads and persists users in SQLite (keeps an in‑memory cache).
- Ensures
QSECOFR
user on init. signOn(user, password)
,signOff()
,getCurrentUser()
.checkAuthority(user, objectId, authority)
: validates*EXECUTE
/*READ
/*CHANGE
/*ALL
considering user, groups, and special authorities (e.g.,*ALLOBJ
).ProgramManager.runProgram()
usescheckAuthority
to validate execution authority.
File: src/kernel/core/Program.js
Lifecycle:
run(job, terminal, parameters)
: callsinitialize()
thenexecute()
; catches errors and callshandleError()
; setsreturnCode
.- Subclasses implement
async execute()
.
Kernel APIs inside a Program:
- Messages:
sendMessage(queue, msg)
,receiveMessage(queue, timeout)
. - Jobs:
createJob(user, subsystem, jobName)
. - Auth/User:
checkAuthority()
,getCurrentUser()
. - System:
getSystemStatus()
.
Terminal APIs exposed via Program:
writeToTerminal(row, col, text, attr?, color?)
.clearTerminal()
.setCursor(row, col)
.createInputField(row, col, length, name, value?)
.waitForInput()
(orterminal.waitForSubmit()
when available).- Default error handling:
handleError(error)
writes at the last line with highlight.
Note: the Terminal
interface is used indirectly by Program (see Program
delegating to terminal.writeAt
, clearScreen
, etc.).
- Create the program file exporting a
default
class extendingProgram
.
Example (simplified):
// src/apps/system/HELLOWLD.js
import { Program } from '../../kernel/core/Program.js';
export default class HELLOWLD extends Program {
constructor(system) {
super('HELLOWLD', 'QGPL', system);
this.description = 'Sample Hello World';
}
async execute() {
this.clearTerminal();
this.writeToTerminal(2, 2, 'Hello, WebOS/400!');
this.setCursor(24, 2);
await this.waitForInput(); // waits for Enter/Fxx depending on Terminal support
}
}
- Register the program in
ProgramManager
.
Option A — Dynamic registration at runtime:
// Somewhere after boot (e.g., in an installer program or console)
const { system } = window.webOS;
const { default: HELLOWLD } = await import('./src/apps/system/HELLOWLD.js');
await system.programManager.registerProgram('HELLOWLD', HELLOWLD, 'QGPL');
Option B — Static registration: add the import in registerSystemPrograms()
inside ProgramManager
.
- Follow the pattern for
SIGNON
,MAIN
,SYSSTATUS
, etc. - After registration, the program is added to the library and cataloged in
programs
(SQLite) automatically.
- Running the program
- Use
ProgramManager.runProgram(name, job, terminal, parameters?)
. - Typically a menu program invokes others passing the active
job
andterminal
. - For console/dev usage: create a job in
QINTER
and invoke from a caller program that owns the terminal.
*CURLIB/*LIBL resolution:
- Calling
runProgram('HELLOWLD', ...)
without qualification resolves via the job’s current library and library list. - You can change
currentLibrary
andlibraryList
on theJob
as needed (e.g.,job.addToLibraryList('MYLIB')
).
Authority:
ProgramManager
validates*EXECUTE
for the current user.QSECOFR
has full authority by default.
Relevant programs: AUDITJRNP
, DSPAUDLOG
, DSPAUDMON
(in src/apps/system/
).
AUDITJRNP
: batch job started during boot (inSystem.initialize()
), listens toQSYSMSG
events and persists toaudit_log
. De‑duplication byevent_id
(unique index + in‑memory processed set).DSPAUDLOG
: viewer/searcher for persisted audit log (SQLite).DSPAUDMON
: real‑time monitor, listening to new queue messages (MQ listener fanout) and displaying live.
MQ semantics applied to auditing:
- Listeners receive a broadcast fanout of every message on a queue.
receiveMessage()
delivers each message to a single consumer (FIFO).
Prerequisite: Python 3 available on PATH.
- Install dependencies: there are no NPM deps; the project uses
sql.js
via CDN. - Start a local HTTP server:
npm run start
# or
npm run dev
# or
python3 -m http.server 8080
- Open
http://localhost:8080
and wait for the SIGNON screen.
At runtime:
- The system object is accessible at
window.webOS.system
(useful for console tests).
Persistence:
- The SQLite DB is saved in the browser’s IndexedDB under key
webos400.db
. To reset, clear site data in DevTools (Application -> Storage -> Clear site data).
-
Program not found
- Ensure it is registered in
ProgramManager
and cataloged in the DB. - For unqualified calls, confirm it’s in the job’s *LIBL.
- Ensure it is registered in
-
Authority error when running a program
- Ensure the current user has
*EXECUTE
on the program object (AuthorityManager
). QSECOFR
has full authority.
- Ensure the current user has
-
Messages not reaching the listener
- Confirm
addListener(queue, fn)
on the correct queue and that messages are sent withsendMessage
.
- Confirm
-
DB “doesn’t persist”
- After changes via
DatabaseManager.execute
, callpersist()
when needed (kernel managers already do at critical points).
- After changes via
-
Terminal/UI
- Programs output via
writeToTerminal
and often wait withwaitForInput()
. The Terminal UI auto‑fits the container and uses an 80x24 fixed‑cell grid.
- Programs output via
- Boot:
src/kernel/bootstrap.js
- Kernel:
src/kernel/core/System.js
- Programs:
src/kernel/core/Program.js
,src/kernel/core/ProgramManager.js
- Jobs:
src/kernel/core/Job.js
,src/kernel/core/JobManager.js
- Subsystems:
src/kernel/core/Subsystem.js
,src/kernel/core/SubsystemManager.js
- Libraries:
src/kernel/core/Library.js
,src/kernel/core/LibraryManager.js
- Messages:
src/kernel/core/MessageQueue.js
- Authority:
src/kernel/core/AuthorityManager.js
- DB:
src/kernel/core/DatabaseManager.js
- UI:
src/ui/components/Terminal.js
,src/ui/SystemUI.js