Expand bounded awk/expr/test toward Issue 7; refresh man, profile 1.0.18,

posix matrix/dashboard, and syscalls/process_table schema alignment (v8).

Booter: replication_operator_sketch/corestore hints, HRPC allowlist tests,
Protomux cap channel 65536-byte bound + export, Wasm posix_profile_peek,
swarm-disk and security_posture docs.

Coreutils/kernel: pkg-swarm-index pathCapabilityEnvelopeVerify on get;
pathcap-verify --trusted failure hint; rebuild bins and sync seeder.

Docs: KERNEL_CONTRACT, kernel-extensions, capabilities index, environment
appendix (warm-cache tuning, cap channel, Wasm env), handbook observability,
vault threat model (multisig), developer-guide ctx/HRPC/Wasm, DOCUMENTATION
release-checklist note, release-checklist optional tier1 drift.

Changelog maintenance in bare-os-booter and bare-os-protocol.
This commit is contained in:
Raven Scott
2026-04-05 23:01:54 -04:00
parent 8f61d0bea8
commit 071edccfb3
73 changed files with 1730 additions and 832 deletions
+171 -95
View File
@@ -91,7 +91,8 @@ function bareOsEmitRaw(ctx, chunk) {
* POSIX-oriented awk interpreter for Bare OS (no import; concatenated before src/awk.js).
* Supports: BEGIN/END, /re/, line patterns, {}, print/printf, if/else, while, for(;;), for(x in a),
* delete arr[idx], next, exit, break, continue, ++/--, arrays, strings, regex ~ !~, builtins (length, substr, index,
* split, sprintf, sub, gsub, match, int, tolower, toupper, rand, srand), getline from stdin/files,
* split, sprintf, sub, gsub, match, int, atan2, cos, sin, exp, log, sqrt, tolower, toupper, rand, srand),
* getline from stdin, files (getline x < \"path\"), unary minus,
* optional fixed-width fields via FIELDWIDTHS when BARE_OS_AWK_FIELDWIDTHS is enabled,
* -F FS, NF, NR, FNR, $0..$n, OFS, ORS, RS, ARGC, ARGV, FILENAME, ENVIRON (read-only mirror).
* GNU-style empty fields: consecutive FS delimiters still advance $1..$NF where the grammar allows.
@@ -670,7 +671,11 @@ class BareAwkParser {
}
parseUnary() {
if (this.peek().value === '!' || this.peek().value === '+') {
if (
this.peek().value === '!' ||
this.peek().value === '+' ||
this.peek().value === '-'
) {
const op = String(this.peek().value)
this.p++
return { t: 'unop', op, a: this.parseUnary() }
@@ -819,6 +824,8 @@ class BareAwkRuntime {
this._stdinIx = 0
/** @type {{ path: string, data: string, append: boolean }[]} */
this.pendingWrites = []
/** @type {Map<string, { lines: string[], i: number }>} */
this._getlineFileState = new Map()
}
splitFields() {
@@ -889,14 +896,14 @@ class BareAwkRuntime {
/**
* @param {BareAwkNode | null} pat
*/
patternMatch(pat) {
async patternMatch(pat) {
if (pat == null) return true
if (pat.t === 'pat') return false
if (pat.t === 'patRe') {
pat.rx.lastIndex = 0
return pat.rx.test(this.$0)
}
if (pat.t === 'patExpr') return this.truthy(this.evalExpr(pat.e))
if (pat.t === 'patExpr') return this.truthy(await this.evalExpr(pat.e))
return false
}
@@ -910,7 +917,7 @@ class BareAwkRuntime {
/**
* @param {BareAwkNode} n
*/
evalExpr(n) {
async evalExpr(n) {
if (!n) return ''
switch (n.t) {
case 'num':
@@ -932,9 +939,9 @@ class BareAwkRuntime {
return this.vars[name] != null ? this.vars[name] : ''
}
case 'field':
return this.fieldNum(this.evalExpr(n.e))
return this.fieldNum(await this.evalExpr(n.e))
case 'index': {
const k = String(this.evalExpr(n.idx))
const k = String(await this.evalExpr(n.idx))
if (n.name === 'ENVIRON') {
const v = this.environ[k]
return v != null ? v : ''
@@ -944,8 +951,8 @@ class BareAwkRuntime {
return arr[k] != null ? arr[k] : ''
}
case 'binop': {
const a = this.evalExpr(n.a)
const b = this.evalExpr(n.b)
const a = await this.evalExpr(n.a)
const b = await this.evalExpr(n.b)
const an = Number(a)
const bn = Number(b)
switch (n.op) {
@@ -978,77 +985,78 @@ class BareAwkRuntime {
}
}
case 'unop': {
const a = this.evalExpr(n.a)
const a = await this.evalExpr(n.a)
if (n.op === '!') return this.truthy(a) ? 0 : 1
if (n.op === '+') return Number(a) || 0
if (n.op === '-') return -(Number(a) || 0)
return 0
}
case '||':
return this.truthy(this.evalExpr(n.a))
return this.truthy(await this.evalExpr(n.a))
? 1
: this.truthy(this.evalExpr(n.b))
: this.truthy(await this.evalExpr(n.b))
? 1
: 0
case '&&':
return this.truthy(this.evalExpr(n.a)) &&
this.truthy(this.evalExpr(n.b))
return this.truthy(await this.evalExpr(n.a)) &&
this.truthy(await this.evalExpr(n.b))
? 1
: 0
case 'match': {
const s = String(this.evalExpr(n.a))
const s = String(await this.evalExpr(n.a))
const rhs = n.b
let rx
if (rhs.t === 'rxLit') rx = rhs.rx
else {
const t = String(this.evalExpr(rhs))
const t = String(await this.evalExpr(rhs))
rx = bareAwkMakeRx(t, '')
}
rx.lastIndex = 0
return rx.test(s) ? 1 : 0
}
case 'nmatch': {
const s = String(this.evalExpr(n.a))
const s = String(await this.evalExpr(n.a))
const rhs = n.b
let rx
if (rhs.t === 'rxLit') rx = rhs.rx
else {
const t = String(this.evalExpr(rhs))
const t = String(await this.evalExpr(rhs))
rx = bareAwkMakeRx(t, '')
}
rx.lastIndex = 0
return rx.test(s) ? 0 : 1
}
case '?:':
return this.truthy(this.evalExpr(n.n))
? this.evalExpr(n.a)
: this.evalExpr(n.b)
return this.truthy(await this.evalExpr(n.n))
? await this.evalExpr(n.a)
: await this.evalExpr(n.b)
case 'assign': {
const v = this.evalExpr(n.right)
this.assign(n.left, n.op, v)
const v = await this.evalExpr(n.right)
await this.assign(n.left, n.op, v)
return v
}
case 'pre': {
const cur = Number(this.evalExpr(n.x))
const cur = Number(await this.evalExpr(n.x))
const next = n.op === '++' ? cur + 1 : cur - 1
this.assignScalar(n.x, next)
await this.assignScalar(n.x, next)
return next
}
case 'post': {
const cur = Number(this.evalExpr(n.x))
const cur = Number(await this.evalExpr(n.x))
const next = n.op === '++' ? cur + 1 : cur - 1
this.assignScalar(n.x, next)
await this.assignScalar(n.x, next)
return cur
}
case 'call':
return this.callBuiltin(n.name, n.args)
return await this.callBuiltin(n.name, n.args)
case 'getline':
return this.doGetline(n)
return await this.doGetline(n)
default:
return ''
}
}
assignScalar(x, v) {
async assignScalar(x, v) {
if (x.t === 'var') {
const name = x.name
if (name === 'FS') this.FS = String(v)
@@ -1057,7 +1065,7 @@ class BareAwkRuntime {
else if (name === 'RS') this.RS = String(v)
else this.vars[name] = v
} else if (x.t === 'field') {
this.setField(this.evalExpr(x.e), v)
this.setField(await this.evalExpr(x.e), v)
}
}
@@ -1066,10 +1074,10 @@ class BareAwkRuntime {
* @param {string} op
* @param {unknown} v
*/
assign(left, op, v) {
async assign(left, op, v) {
let base = v
if (op !== '=') {
const cur = Number(this.evalExpr(left))
const cur = Number(await this.evalExpr(left))
const nv = Number(v)
if (op === '+=') base = cur + nv
else if (op === '-=') base = cur - nv
@@ -1080,32 +1088,52 @@ class BareAwkRuntime {
if (left.t === 'index') {
const arr =
this.arrays[left.name] || (this.arrays[left.name] = Object.create(null))
arr[String(this.evalExpr(left.idx))] = base
arr[String(await this.evalExpr(left.idx))] = base
return
}
this.assignScalar(left, base)
await this.assignScalar(left, base)
}
/**
* @param {string} path
*/
async _ensureGetlineFile(path) {
let st = this._getlineFileState.get(path)
if (st) return st
const buf = await this.io.readFile(path)
const text =
buf && this.io.bytesToString ? this.io.bytesToString(buf) : ''
const lines = text.split(/\r?\n/)
if (lines.length && lines[lines.length - 1] === '') lines.pop()
st = { lines, i: 0 }
this._getlineFileState.set(path, st)
return st
}
/**
* @param {string} name
* @param {BareAwkNode[]} args
*/
callBuiltin(name, args) {
const a = (i) => (args[i] ? this.evalExpr(args[i]) : '')
async callBuiltin(name, args) {
const a = async (i) =>
args[i] != null ? await this.evalExpr(args[i]) : ''
switch (name) {
case 'length':
if (!args.length) return this.$0.length
return String(a(0)).length
case 'substr':
return String(a(0)).slice(
Math.max(0, Math.trunc(Number(a(1))) - 1),
Math.max(0, Math.trunc(Number(a(1))) - 1 + Math.trunc(Number(a(2))))
)
return String(await a(0)).length
case 'substr': {
const s = String(await a(0))
const start = Math.max(0, Math.trunc(Number(await a(1))) - 1)
if (args.length < 3) return s.slice(start)
const ln = Math.trunc(Number(await a(2)))
if (!Number.isFinite(ln) || ln < 0) return ''
return s.slice(start, start + ln)
}
case 'index':
return String(a(0)).indexOf(String(a(1))) + 1 || 0
return String(await a(0)).indexOf(String(await a(1))) + 1 || 0
case 'split': {
const s = String(a(0))
const fs = args[1] ? String(this.evalExpr(args[1])) : this.FS
const s = String(await a(0))
const fs = args[1] ? String(await this.evalExpr(args[1])) : this.FS
let rx = fs.length === 1 ? null : bareAwkMakeRx(fs, '')
const parts = rx ? s.split(rx) : s.split(fs)
const aname = /** @type {BareAwkNode} */ (args[2])
@@ -1116,17 +1144,35 @@ class BareAwkRuntime {
}
return parts.length
}
case 'sprintf':
return bareAwkSprintf(
String(a(0)),
args.slice(1).map((x) => this.evalExpr(x))
)
case 'sprintf': {
const fmt = String(await a(0))
const rest = []
for (let j = 1; j < args.length; j++)
rest.push(await this.evalExpr(args[j]))
return bareAwkSprintf(fmt, rest)
}
case 'int':
return Math.trunc(Number(a(0)))
return Math.trunc(Number(await a(0)))
case 'log': {
const x = Number(await a(0))
return x > 0 ? Math.log(x) : Number.NaN
}
case 'sqrt': {
const x = Number(await a(0))
return x >= 0 ? Math.sqrt(x) : Number.NaN
}
case 'sin':
return Math.sin(Number(await a(0)))
case 'cos':
return Math.cos(Number(await a(0)))
case 'exp':
return Math.exp(Number(await a(0)))
case 'atan2':
return Math.atan2(Number(await a(0)), Number(await a(1)))
case 'tolower':
return String(a(0)).toLowerCase()
return String(await a(0)).toLowerCase()
case 'toupper':
return String(a(0)).toUpperCase()
return String(await a(0)).toUpperCase()
case 'rand': {
this.randSeed = (this.randSeed * 1103515245 + 12345) & 0x7fffffff
return this.randSeed / 0x7fffffff
@@ -1140,9 +1186,11 @@ class BareAwkRuntime {
case 'sub':
case 'gsub': {
const rx =
args[0].t === 'rxLit' ? args[0].rx : bareAwkMakeRx(String(a(0)), '')
const rep = String(a(1))
let target = args[2] ? String(a(2)) : this.$0
args[0].t === 'rxLit'
? args[0].rx
: bareAwkMakeRx(String(await a(0)), '')
const rep = String(await a(1))
let target = args[2] ? String(await a(2)) : this.$0
let n = 0
if (name === 'sub') {
rx.lastIndex = 0
@@ -1167,9 +1215,11 @@ class BareAwkRuntime {
return n
}
case 'match': {
const s = String(a(0))
const s = String(await a(0))
const rx =
args[1].t === 'rxLit' ? args[1].rx : bareAwkMakeRx(String(a(1)), '')
args[1].t === 'rxLit'
? args[1].rx
: bareAwkMakeRx(String(await a(1)), '')
rx.lastIndex = 0
const m = rx.exec(s)
if (!m) {
@@ -1183,7 +1233,7 @@ class BareAwkRuntime {
}
default:
if (this.funcs.has(name)) {
return this.callUser(name, args)
return await this.callUser(name, args)
}
bareAwkError('unknown function ' + name)
}
@@ -1193,19 +1243,19 @@ class BareAwkRuntime {
* @param {string} name
* @param {BareAwkNode[]} args
*/
callUser(name, args) {
async callUser(name, args) {
const f = this.funcs.get(name)
if (!f) return ''
const frame = { ...this.vars }
for (let i = 0; i < f.params.length; i++) {
frame[f.params[i]] = args[i] ? this.evalExpr(args[i]) : ''
frame[f.params[i]] = args[i] ? await this.evalExpr(args[i]) : ''
}
const prev = this.vars
this.vars = frame
let ret = ''
try {
for (const st of f.body) {
const r = this.execStmt(st)
const r = await this.execStmt(st)
if (r && r.t === 'return') {
ret = r.v != null ? r.v : ''
break
@@ -1220,12 +1270,27 @@ class BareAwkRuntime {
/**
* @param {BareAwkNode} n
*/
doGetline(n) {
async doGetline(n) {
let line = null
if (n.from) {
const path = String(this.evalExpr(n.from))
/* sync read — awk.js should preload or use async wrapper; runtime uses promise in run loop */
return 0
const path = String(await this.evalExpr(n.from))
const st = await this._ensureGetlineFile(path)
if (st.i < st.lines.length) line = st.lines[st.i++]
else line = null
if (line == null) {
if (n.var) this.vars[n.var] = ''
else {
this.$0 = ''
this.splitFields()
}
return 0
}
if (n.var) this.vars[n.var] = line
else {
this.$0 = line
this.splitFields()
}
return 1
}
if (this._stdinIx < this.io.stdinLines.length) {
line = this.io.stdinLines[this._stdinIx++]
@@ -1245,23 +1310,24 @@ class BareAwkRuntime {
/**
* @param {BareAwkNode} st
* @returns {{ t: string, v?: unknown } | void}
* @returns {Promise<{ t: string, v?: unknown } | void>}
*/
execStmt(st) {
async execStmt(st) {
switch (st.t) {
case 'block':
for (const s of st.stmts) {
const r = this.execStmt(s)
const r = await this.execStmt(s)
if (r) return r
}
break
case 'if':
if (this.truthy(this.evalExpr(st.cond))) return this.execStmt(st.then)
if (st.else) return this.execStmt(st.else)
if (this.truthy(await this.evalExpr(st.cond)))
return await this.execStmt(st.then)
if (st.else) return await this.execStmt(st.else)
break
case 'while':
while (this.truthy(this.evalExpr(st.cond))) {
const r = this.execStmt(st.body)
while (this.truthy(await this.evalExpr(st.cond))) {
const r = await this.execStmt(st.body)
if (r?.t === 'break') break
if (r?.t === 'continue') continue
if (r?.t === 'next' || r?.t === 'nextfile' || r?.t === 'exit')
@@ -1269,25 +1335,25 @@ class BareAwkRuntime {
}
break
case 'for':
if (st.init) this.evalExpr(st.init)
if (st.init) await this.evalExpr(st.init)
while (true) {
if (st.cond && !this.truthy(this.evalExpr(st.cond))) break
const r = this.execStmt(st.body)
if (st.cond && !this.truthy(await this.evalExpr(st.cond))) break
const r = await this.execStmt(st.body)
if (r?.t === 'break') break
if (r?.t === 'continue') {
if (st.step) this.evalExpr(st.step)
if (st.step) await this.evalExpr(st.step)
continue
}
if (r?.t === 'next' || r?.t === 'nextfile' || r?.t === 'exit')
return r
if (st.step) this.evalExpr(st.step)
if (st.step) await this.evalExpr(st.step)
}
break
case 'forin': {
const arr = this.arrays[st.arr] || {}
for (const k of Object.keys(arr)) {
this.vars[st.iv] = k
const r = this.execStmt(st.body)
const r = await this.execStmt(st.body)
if (r?.t === 'break') break
if (r?.t === 'continue') continue
if (r?.t === 'next' || r?.t === 'nextfile' || r?.t === 'exit')
@@ -1298,7 +1364,7 @@ class BareAwkRuntime {
case 'delete': {
const tbl =
this.arrays[st.arr] || (this.arrays[st.arr] = Object.create(null))
const ky = String(this.evalExpr(st.key))
const ky = String(await this.evalExpr(st.key))
delete tbl[ky]
break
}
@@ -1311,19 +1377,23 @@ class BareAwkRuntime {
case 'continue':
return { t: 'continue' }
case 'exit':
this.exitCode = Math.trunc(Number(this.evalExpr(st.code))) || 0
this.exitCode =
Math.trunc(Number(await this.evalExpr(st.code))) || 0
this.exitPending = true
return { t: 'exit' }
case 'return':
return { t: 'return', v: st.e ? this.evalExpr(st.e) : '' }
return {
t: 'return',
v: st.e ? await this.evalExpr(st.e) : ''
}
case 'print':
this.queuePrint(st.args, st.redir, false)
await this.queuePrint(st.args, st.redir, false)
break
case 'printf':
this.queuePrint([st.fmt, ...st.args], st.redir, true)
await this.queuePrint([st.fmt, ...st.args], st.redir, true)
break
case 'expr':
this.evalExpr(st.e)
await this.evalExpr(st.e)
break
}
}
@@ -1333,17 +1403,23 @@ class BareAwkRuntime {
* @param {{ op: string, file: BareAwkNode } | null} redir
* @param {boolean} isPrintf
*/
queuePrint(args, redir, isPrintf) {
async queuePrint(args, redir, isPrintf) {
let s = ''
if (isPrintf) {
const fmt = String(this.evalExpr(args[0]))
const rest = args.slice(1).map((x) => this.evalExpr(x))
const fmt = String(await this.evalExpr(args[0]))
const rest = []
for (let j = 1; j < args.length; j++)
rest.push(await this.evalExpr(args[j]))
s = bareAwkSprintf(fmt, rest)
} else if (!args.length) s = this.$0
else s = args.map((x) => String(this.evalExpr(x))).join(this.OFS)
else {
const parts = []
for (const x of args) parts.push(String(await this.evalExpr(x)))
s = parts.join(this.OFS)
}
s += this.ORS
if (redir) {
const path = String(this.evalExpr(redir.file))
const path = String(await this.evalExpr(redir.file))
this.pendingWrites.push({
path,
data: s,
@@ -1406,7 +1482,7 @@ async function bareAwkRun(program, opts, io) {
rule.pattern.k === 'BEGIN'
) {
for (const st of rule.stmts) {
const r = rt.execStmt(st)
const r = await rt.execStmt(st)
await rt.flushWrites()
if (r?.t === 'exit') return rt.exitCode
}
@@ -1432,9 +1508,9 @@ async function bareAwkRun(program, opts, io) {
rt.splitFields()
for (const rule of ast.rules) {
if (rule.pattern && rule.pattern.t === 'pat') continue
if (!rt.patternMatch(rule.pattern)) continue
if (!(await rt.patternMatch(rule.pattern))) continue
for (const st of rule.stmts) {
const r = rt.execStmt(st)
const r = await rt.execStmt(st)
await rt.flushWrites()
if (r?.t === 'next') break
if (r?.t === 'nextfile') break lines
@@ -1460,7 +1536,7 @@ async function bareAwkRun(program, opts, io) {
for (const rule of ast.rules) {
if (rule.pattern && rule.pattern.t === 'pat' && rule.pattern.k === 'END') {
for (const st of rule.stmts) {
rt.execStmt(st)
await rt.execStmt(st)
await rt.flushWrites()
}
}
+23 -3
View File
@@ -91,7 +91,7 @@ async function run(ctx, argv) {
const tokens = argv.slice(1)
if (!tokens.length || tokens[0] === '--help' || tokens[0] === '-h') {
ctx.console.log(
'usage: expr EXPRESSION\nInteger arithmetic (+ - * / %), comparisons, and string = / !=.'
'usage: expr EXPRESSION\nInteger arithmetic (+ - * / %), comparisons, string = / !=, and POSIX : (regex match length; pattern is ECMA ^(?:…))'
)
return
}
@@ -108,11 +108,31 @@ async function run(ctx, argv) {
if (/^-?\d+$/.test(t)) return { kind: 'n', v: parseInt(t, 10) }
return { kind: 's', v: t }
}
function parseMul() {
/** POSIX-style `:` regex match length (pattern is ECMA RegExp body after ^). */
function parseColon() {
let left = parsePrimary()
while (peek() === ':') {
take()
const right = parsePrimary()
const s = left.kind === 'n' ? String(left.v) : String(left.v)
const pat = right.kind === 'n' ? String(right.v) : String(right.v)
let n = 0
try {
const re = new RegExp('^(?:' + pat + ')')
const m = re.exec(s)
if (m) n = m[0].length
} catch (_) {
n = 0
}
left = { kind: 'n', v: n }
}
return left
}
function parseMul() {
let left = parseColon()
while (peek() === '*' || peek() === '/' || peek() === '%') {
const op = take()
const right = parsePrimary()
const right = parseColon()
if (left.kind !== 'n' || right.kind !== 'n') throw new Error('non-numeric')
if (op === '*') left = { kind: 'n', v: left.v * right.v }
else if (op === '/') {
+5
View File
@@ -172,5 +172,10 @@ async function run(ctx, argv) {
return
}
ctx.console.error('pathcap-verify: FAIL ' + r.reason)
if (trusted) {
ctx.console.error(
'pathcap-verify: hint: extend BARE_OS_PATH_CAPABILITY_TRUSTED_PUBKEYS_HEX with the issuer Ed25519 pubkey (64 hex) when the envelope is otherwise well-formed.'
)
}
ctx.exitCode = 1
}
+21
View File
@@ -191,6 +191,27 @@ ${topic ? 'Topic pin prefix: ' + topic.slice(0, 16) + '…' : 'Topic pin unset.'
return
}
const r = await pkgIndexLookup(ctx, keyArg)
const ent =
r && typeof r === 'object' && r.entry && typeof r.entry === 'object'
? r.entry
: null
if (
ent &&
ent.pathCapabilityEnvelope != null &&
typeof ctx.bareOsVerifyPathCapabilityEnvelope === 'function'
) {
try {
r.pathCapabilityEnvelopeVerify =
ctx.bareOsVerifyPathCapabilityEnvelope(
ent.pathCapabilityEnvelope
)
} catch (e) {
r.pathCapabilityEnvelopeVerify = {
ok: false,
error: (e && e.message) || String(e)
}
}
}
ctx.console.log(JSON.stringify(r, null, 2))
if (!r.ok) ctx.exitCode = 1
return
+19
View File
@@ -156,6 +156,25 @@ async function evalTest(ctx, args) {
}
if (op === '=') return a === b
if (op === '!=') return a !== b
if (op === '-nt' || op === '-ot' || op === '-ef') {
const s1 = await ctx.vfs.stat(a)
const s2 = await ctx.vfs.stat(b)
if (!s1 || !s2) return false
if (op === '-ef') {
const i1 = s1.ino != null ? String(s1.ino) : ''
const i2 = s2.ino != null ? String(s2.ino) : ''
const d1 = s1.dev != null ? String(s1.dev) : ''
const d2 = s2.dev != null ? String(s2.dev) : ''
if (i1 && i2 && d1 && d2) return i1 === i2 && d1 === d2
return a === b
}
const t1 = Number(s1.mtimeMs)
const t2 = Number(s2.mtimeMs)
const m1 = Number.isFinite(t1) ? t1 : 0
const m2 = Number.isFinite(t2) ? t2 : 0
if (op === '-nt') return m1 > m2
if (op === '-ot') return m1 < m2
}
return false
}
if (args.length === 2) {