Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 9 additions & 4 deletions .claude/skills/limbo-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ cd appl/cmd; mk install # installs to the canonical path
```

If a fix "isn't taking effect" — same bug after recompile, diagnostic
prints not appearing — run `tools/verify-dis-paths.sh` before chasing
anything else. The pre-commit hook (`./hooks/install.sh`) and CI both run
it.
prints not appearing — you almost certainly compiled to a path nothing
loads. `dis/` is a build product and is not tracked; rebuild it properly:

for d in appl appl/mpeg appl/veltro tests; do (cd $d && mk install); done

Never hand-roll `limbo -o <path>`; use `tools/compile-limbo.sh` or
`mk install`. `tools/verify-dis-build.sh` (run by CI) checks the build
against `tools/dis-manifest.txt`.

**2. A changed `.m` interface stales every dependent `.dis`.** The Dis VM
rejects stale modules at load time with `link typecheck` errors — blank
Expand Down Expand Up @@ -71,7 +76,7 @@ Verify every modified file's pre-image matches master tip (rebase, then
`git diff master...HEAD` and check the base blobs). A branch cut from — or
contaminated by — another unmerged branch can carry someone else's change
through a textually clean merge, silently. Then run
`tools/verify-dis-paths.sh` and fill the PR template's design-principles
`tools/verify-dis-build.sh` and fill the PR template's design-principles
checklist honestly.

## Running what you built
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/limbo-test/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,6 @@ assuming it exercises behavior.
suite passed — read the job log when it matters.
- Gating: nsaudit fixture checks, wallet/secstore integration, the
presentation file-open GUI regression, JIT correctness and boot smoke,
`verify-dis-paths`, `verify-sh-exec`, and the ring-fence job.
`verify-dis-build`, `verify-sh-exec`, and the ring-fence job.
- Benchmarks (`tests/bench/`), stress (`tests/stress/`), and interop
(`tests/interop/`) are deliberately outside the auto-run suite.
48 changes: 48 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ jobs:
echo "WARNING: libfido2 not linked — /dev/2fa will be a stub in this artifact" >&2
fi

# dis/ is not tracked, so the tree must be built before it can be
# staged -- exactly as emu/*/o.emu already is. This also checks the
# build against tools/dis-manifest.txt, so a release cannot quietly
# ship with a module missing.
PATH="$ROOT/$(uname -s | sed 's/Darwin/MacOSX/')/$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')/bin:$PATH"
export PATH
"$ROOT/tools/verify-dis-build.sh"

# Shared runtime tree
for d in dis lib fonts module services locale usr mnt; do
[ -d "$d" ] && cp -a "$d" "$STAGE/"
Expand Down Expand Up @@ -449,6 +457,14 @@ jobs:
cp Linux/amd64/bin/limbo Linux/amd64/bin/mk "$STAGE/bin/"
strip "$STAGE/bin/emu" "$STAGE/bin/limbo" "$STAGE/bin/mk"

# dis/ is not tracked, so the tree must be built before it can be
# staged -- exactly as emu/*/o.emu already is. This also checks the
# build against tools/dis-manifest.txt, so a release cannot quietly
# ship with a module missing.
PATH="$ROOT/$(uname -s | sed 's/Darwin/MacOSX/')/$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')/bin:$PATH"
export PATH
"$ROOT/tools/verify-dis-build.sh"

# Runtime tree into resources/
for d in dis lib fonts module services locale usr mnt; do
[ -d "$d" ] && cp -a "$d" "$STAGE/resources/"
Expand Down Expand Up @@ -643,6 +659,14 @@ jobs:
echo "WARNING: libfido2 not linked — /dev/2fa will be a stub in this artifact" >&2
fi

# dis/ is not tracked, so the tree must be built before it can be
# staged -- exactly as emu/*/o.emu already is. This also checks the
# build against tools/dis-manifest.txt, so a release cannot quietly
# ship with a module missing.
PATH="$ROOT/$(uname -s | sed 's/Darwin/MacOSX/')/$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')/bin:$PATH"
export PATH
"$ROOT/tools/verify-dis-build.sh"

# Shared runtime tree
for d in dis lib fonts module services locale usr mnt; do
[ -d "$d" ] && cp -a "$d" "$STAGE/"
Expand Down Expand Up @@ -919,6 +943,14 @@ jobs:
# Native tools
cp Linux/arm64/bin/limbo Linux/arm64/bin/mk "$STAGE/bin/"

# dis/ is not tracked, so the tree must be built before it can be
# staged -- exactly as emu/*/o.emu already is. This also checks the
# build against tools/dis-manifest.txt, so a release cannot quietly
# ship with a module missing.
PATH="$ROOT/$(uname -s | sed 's/Darwin/MacOSX/')/$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')/bin:$PATH"
export PATH
"$ROOT/tools/verify-dis-build.sh"

# Runtime tree into resources/
for d in dis lib fonts module services locale usr mnt; do
[ -d "$d" ] && cp -a "$d" "$STAGE/resources/"
Expand Down Expand Up @@ -1115,6 +1147,14 @@ jobs:
# Strip debug symbols before signing
strip "$MACOS/emu" "$MACOS/tools/limbo" "$MACOS/tools/mk"

# dis/ is not tracked, so the tree must be built before it can be
# staged -- exactly as emu/*/o.emu already is. This also checks the
# build against tools/dis-manifest.txt, so a release cannot quietly
# ship with a module missing.
PATH="$ROOT/$(uname -s | sed 's/Darwin/MacOSX/')/$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')/bin:$PATH"
export PATH
"$ROOT/tools/verify-dis-build.sh"

# Runtime tree goes in Contents/Resources/
for d in dis lib fonts module services locale usr mnt; do
[ -d "$d" ] && cp -a "$d" "$RESOURCES/"
Expand Down Expand Up @@ -1336,6 +1376,14 @@ jobs:
cp MacOSX/arm64/bin/limbo MacOSX/arm64/bin/mk "$STAGE/MacOSX/arm64/bin/"
strip "$STAGE/emu/MacOSX/o.emu" "$STAGE/MacOSX/arm64/bin/limbo" "$STAGE/MacOSX/arm64/bin/mk" 2>/dev/null || true

# dis/ is not tracked, so the tree must be built before it can be
# staged -- exactly as emu/*/o.emu already is. This also checks the
# build against tools/dis-manifest.txt, so a release cannot quietly
# ship with a module missing.
PATH="$ROOT/$(uname -s | sed 's/Darwin/MacOSX/')/$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')/bin:$PATH"
export PATH
"$ROOT/tools/verify-dis-build.sh"

# Shared runtime tree
for d in dis lib fonts module services locale usr mnt; do
[ -d "$d" ] && cp -a "$d" "$STAGE/"
Expand Down
42 changes: 42 additions & 0 deletions .github/workflows/verify-dis-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Verify dis build

# dis/ is a build product and is not tracked, so it cannot go stale. What
# it CAN do is quietly lose a module: drop a target from an mkfile's TARG
# and the module simply stops being built, with nothing to say so until
# something fails to load at runtime. 45 modules were already in that
# state and only kept working because their bytecode happened to be
# committed.
#
# tools/dis-manifest.txt tracks the set of modules the build must produce.
# This job builds the tree and compares. A module appearing or vanishing
# is a reviewable one-line diff instead of a binary blob.

on:
pull_request:
push:
branches: [master]

permissions:
contents: read

jobs:
dis-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Build native toolchain
run: |
set -e
export ROOT="$PWD"
SYSTARG=Linux OBJTYPE=amd64 ./makemk.sh
export PATH="$ROOT/Linux/amd64/bin:$PATH"
for d in lib9 libbio libmp libsec libmath utils/iyacc limbo; do
(cd "$d" && mk install)
done

- name: Build the runtime tree and check the manifest
run: |
export ROOT="$PWD"
export PATH="$ROOT/Linux/amd64/bin:$PATH"
./tools/verify-dis-build.sh
27 changes: 0 additions & 27 deletions .github/workflows/verify-dis-paths.yml

This file was deleted.

46 changes: 30 additions & 16 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,36 @@ services/httpd/httpd.log
!.clusterfuzzlite/Dockerfile

# Compiled Limbo bytecode in source tree (build artifacts)
# Note: dis/*.dis (runtime files) are TRACKED, matching upstream Inferno OS
# Compiled Dis bytecode is a BUILD PRODUCT, not source. It is not tracked.
#
# It used to be, inherited from the 2006 upstream drop (46439007c), and the
# tree drifted from the source that produced it: bytecode went stale, modules
# were compiled to paths the build never installs to, sources were deleted
# with their binaries left behind, and 45 modules shipped that no mkfile
# compiled. Releases shipped ~940 files nothing had built from the tagged
# source.
#
# A release still ships a runnable tree -- the packaging job builds it first,
# exactly as it already did for emu/*/o.emu, which was never tracked either.
#
# Rebuild after a pull: for d in appl appl/mpeg appl/veltro tests; do
# (cd $d && mk install); done
# (hooks/post-merge does this for you -- install it with ./hooks/install.sh)
#
# The SET of modules the build must produce IS tracked, as
# tools/dis-manifest.txt, so a module cannot vanish unnoticed.
#
# Only *.dis is ignored: the hand-written Inferno sh scripts that live in
# these trees (acme/dis/Clean, dis/svc/auth, dis/lc, ...) are real sources
# and stay tracked.
dis/**/*.dis
acme/dis/**/*.dis
xenith/dis/**/*.dis
dis/**/*.sbl
# installed by appl/cmd/git/mkfile
dis/cmd/git/log.sh
dis/cmd/git/status.sh
dis/cmd/git/diff.sh
/appl/**/*.dis
/appl/**/*.sbl
# Also exclude tests build artifacts
Expand All @@ -77,15 +106,8 @@ services/httpd/httpd.log
# Root-level build artifacts (stray compiles)
/*.dis
/*.sbl
# Debug symbols in runtime dis tree
/dis/**/*.sbl
# dis/tests/*.dis is TRACKED (runtime tree ships prebuilt, tests included —
# maintainer ruling 2026-08-21); only this stray lib-tree test artifact is not.
/dis/lib/testing_test.dis
# CI/local test output
test-output.txt
# Xenith plugin build artifacts (built from appl/xenith/xenith/)
/xenith/dis/
.env

# Claude Code local settings
Expand Down Expand Up @@ -199,9 +221,6 @@ tmp/.veltro/
# running veltro tests under emu); not source.
usr/inferno/veltro/

# Stale build artifacts placed in wrong location (authoritative copies are in dis/)
dis/cmd/luci*.dis
dis/cmd/luci*.sbl

# Stray files
fprint'
Expand Down Expand Up @@ -254,7 +273,6 @@ usr/inferno/secstore/*/
lib/veltro/welcome_shown
lib/veltro/tour_offered

dis/mermaid_test.dis

# Runtime overlay root — populated by emu at boot, never tracked
n/local/
Expand Down Expand Up @@ -294,10 +312,6 @@ build-android-apk.ndk-x86_64.log
# node interop harness artifacts (generated; never commit keys)
usr/inferno/keyring/interop-*
tmp/interop_pulled_*
dis/interop_node_server.dis
dis/interop_node_client.dis
/dis/tests/stress/
/dis/tests/bench/

# Local video/image capture from codec testing (kept on disk, never committed)
*.m1v
Expand Down
42 changes: 38 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,46 @@ Use the platform scripts instead of inventing local build flows.
- `./build-linux-amd64.sh headless`: Linux build without SDL3.
- `./build-macos-sdl3.sh` or `./build-macos-headless.sh`: macOS builds.
- `powershell -ExecutionPolicy Bypass -File build-windows-amd64.ps1`: Windows build.
- `cd appl/cmd && mk install`: rebuild one Limbo subtree and install tracked runtime output into `dis/`.
- `cd tests && mk install && cd ..`: rebuild Limbo tests into the tracked runtime tree.
- `cd appl/cmd && mk install`: rebuild one Limbo subtree into `dis/`.
- `cd tests && mk install && cd ..`: rebuild the Limbo tests into `dis/tests/`.
- `./run-tests.sh`: run host and emulator tests.
- `./run-tests.sh -h` or `./run-tests.sh -i -v`: run only host tests, or only emulator tests with verbose output.
- `./emu/<Platform>/o.emu -r. /tests/runner.dis -v`: run the native Limbo test runner directly when working inside the emulator workflow.

## Compiled bytecode is not in the repository

`dis/` holds compiled Dis bytecode. **It is a build product and is not
tracked**, exactly like `emu/*/o.emu`. A fresh clone has no runtime until you
build one. Do not commit `.dis` files, and do not treat a missing one as a
bug in the tree.

**Standard procedure after cloning, pulling, or changing any `.b` or `.m`:**

```sh
export ROOT=$PWD
export PATH="$ROOT/$SYSHOST/$OBJTYPE/bin:$PATH" # e.g. Linux/arm64/bin
for d in appl appl/mpeg appl/veltro tests; do (cd $d && mk install); done
```

`hooks/post-merge` does this automatically after `git pull` — install it once
with `./hooks/install.sh`. Rebuilding the whole tree takes about 20 seconds.

Those four directories are the complete set: `appl/mpeg` and `appl/veltro` are
**not** in `appl/mkfile`'s `DIRS`, so `cd appl && mk install` alone does not
reach them. This catches people out; use the loop.

**If a change makes the build produce a different set of modules**, update
`tools/dis-manifest.txt` in the same commit. It lists every module the build
must produce, and `tools/verify-dis-build.sh` (run by CI and by every release
job) fails when one goes missing. Adding a module means adding a target to an
mkfile `TARG` *and* a line to the manifest; removing one means deleting both.
A module that is not in any `TARG` is never compiled — 45 were in that state
and only appeared to work because their bytecode had been committed by hand.

**Never run `limbo -o <path>` by hand.** Use `tools/compile-limbo.sh` or
`mk install` from the source directory. Choosing an output path yourself is
how modules end up somewhere no build installs to and no runtime loads from.

## Coding Style & Naming Conventions

Match the surrounding code closely. Limbo (`.b`) is close to Go in structure but should follow existing Inferno idioms, naming, and control-flow style. C uses Plan 9 / Inferno conventions and tabs, not generic modern C house styles. *Host-side* shell scripts (`tests/host/`, `tools/`, `build-*.sh`) stay POSIX `sh` compatible; scripts that run *inside Inferno* (`tests/inferno/`, `lib/sh/`, boot scripts) are rc-style — no `&&`/`||` — see [docs/INFERNO-SHELL.md](docs/INFERNO-SHELL.md). Name new emulator tests `*_test.b`, host tests `*_test.sh`, and keep module interfaces in `module/` aligned with their implementation names.
Expand All @@ -44,7 +78,7 @@ Use Inferno `mk`, not GNU make, for subtree builds. In `mkfile`s, do not chain c

## Testing Guidelines

Behavior changes should include tests. Prefer the repository’s Limbo test setup instead of ad hoc harnesses: build tests with `mk install`, then run them through `tests/runner.dis` or `./run-tests.sh`. Host integration checks belong in `tests/host/`. If a change regenerates runtime `.dis` files under `dis/`, make sure they result from `mk install` in the corresponding source directory rather than manual edits.
Behavior changes should include tests. Prefer the repository’s Limbo test setup instead of ad hoc harnesses: build tests with `mk install`, then run them through `tests/runner.dis` or `./run-tests.sh`. Host integration checks belong in `tests/host/`. `.dis` files are never committed; if a change adds or removes a module, update `tools/dis-manifest.txt` to match (see above).

## Security Review Priorities

Expand All @@ -60,4 +94,4 @@ When proposing fixes, prefer namespace, mount, process-group, file-permission, a

Recent history uses scoped, imperative subjects such as `fix(theme): ...`, `build: ...`, and `test: ...`. Keep the first line under 72 characters, explain why in the body when needed, and reference issue IDs like `INFR-28` when relevant. PRs should stay focused, describe the motivation, include test coverage, update docs for interface changes, and include screenshots for UI work in Lucia/Xenith.

Know what CI will check before you push: `verify-dis-paths` (every source's declared `.dis` exists and is fresh — the wrong-target trap), `verify-sh-exec` (Inferno-side test scripts committed mode 755), the ring-fence job (`tests/agent-harness/` material must never appear elsewhere), the advisory style gate (rc-violations, JSON inside `appl/`, new file interfaces with no linked proposal issue), plus builds, the test suites, CodeQL, and nsaudit fixture checks. The PR template's design-principles checklist is expected to be filled honestly.
Know what CI will check before you push: `verify-dis-build` (the tree builds and produces every module `tools/dis-manifest.txt` lists), `verify-sh-exec` (Inferno-side test scripts committed mode 755), the ring-fence job (`tests/agent-harness/` material must never appear elsewhere), the advisory style gate (rc-violations, JSON inside `appl/`, new file interfaces with no linked proposal issue), plus builds, the test suites, CodeQL, and nsaudit fixture checks. The PR template's design-principles checklist is expected to be filled honestly.
Loading
Loading