149 lines
7.8 KiB
Markdown
149 lines
7.8 KiB
Markdown
# API_TODO — trans-membrane OS surface expansion
|
|
|
|
Status: briefing / design spec for the next round of `uce_host_*` membrane work.
|
|
Implementation delegated to the pi agent + sub-agents.
|
|
|
|
## Why
|
|
|
|
UCE units run as wasm in a per-request workspace; the **only** way they touch the
|
|
OS is via `uce_host_*` hostcalls (see `docs/wasm-runtime-architecture.md`). That
|
|
constrained, auditable surface is a feature — it's the security boundary
|
|
sysadmins can reason about. The current file API (`file_get_contents` /
|
|
`file_put_contents` / `file_append` / `file_exists` / `file_unlink` / `ls` /
|
|
`file_mkdir` / `file_mtime` / `path_*` / `cwd_*`) is simple and good but is
|
|
**whole-file only** and lacks several OS capabilities real apps need. This doc
|
|
widens *what* units can do **within the existing path roots** — not *where*.
|
|
|
|
## Cross-cutting design principles (apply to EVERY new call)
|
|
|
|
1. **Requests must complete fast — never block indefinitely.** Every potentially
|
|
blocking call takes an explicit timeout/deadline and **fails fast** on expiry
|
|
with a clear result, rather than spinning or blocking forever. In particular:
|
|
- **File locks expire.** Auto-locking (writes take exclusive, reads wait for
|
|
writers) must use a **bounded** wait (deadline, e.g. config
|
|
`UCE_FILE_LOCK_TIMEOUT_MS`, sane default ~2000ms). On timeout the op returns
|
|
a clear "lock timeout" error — it does NOT hang the request. This applies to
|
|
`file_put_contents`/`file_append`/`file_get_contents` and the new streaming
|
|
handles below.
|
|
- `http_request`, blocking `shell_exec`, `job_await`, etc. all take timeouts.
|
|
2. **Fire-and-forget + check back later.** Long work goes to the background and
|
|
the request returns immediately with a **job id**; a *later* request (any
|
|
worker) checks the outcome. A single host-side **job registry** backs this
|
|
(see "Async jobs"). This is the model for background shell, async http, and
|
|
any long op — not per-request blocking.
|
|
3. **Simple AND versatile.** Prefer one small, composable primitive over many
|
|
special cases. Structured inputs/outputs are `DValue` (UCEB across the
|
|
membrane). Streaming uses opaque host **handles** (u64). Errors are returned
|
|
values (no exceptions in the guest).
|
|
4. **One capability per named hostcall** (blocklist granularity — see Future).
|
|
Don't bundle unrelated capabilities into one mega-hostcall.
|
|
5. **Always policy-gated.** Every path goes through `resolve_guest_file` /
|
|
`resolve_guest_write` (roots); handles/stat/rename/etc. gated the same way.
|
|
6. **No exceptions/RTTI** in unit-facing code; return values + status DValues.
|
|
|
|
## API groups to implement
|
|
|
|
### 1. Streaming / handle-based file I/O (P0 — the keystone)
|
|
Whole-file String I/O can't handle large or partial files. Add a handle API.
|
|
Locking stays automatic and **lifetime-scoped** (open-for-write = exclusive,
|
|
open-for-read = shared, released on `file_close`), with the bounded-wait rule
|
|
from principle 1 — this is NOT the old manual lock API, the user never calls a
|
|
lock function.
|
|
```
|
|
u64 file_open(String path, String mode) // "r" "w" "a" "r+"; 0 = error (incl. lock timeout)
|
|
String file_read(u64 h, u64 len) // sequential
|
|
String file_pread(u64 h, u64 offset, u64 len) // positional
|
|
u64 file_write(u64 h, String data) // sequential; returns bytes written
|
|
u64 file_pwrite(u64 h, u64 offset, String data)
|
|
s64 file_seek(u64 h, s64 offset, int whence) // + file_tell(h)
|
|
void file_close(u64 h) // releases lock + fd
|
|
```
|
|
Keep the existing whole-file `file_get_contents`/`put_contents`/`append` (now
|
|
auto-locking) as the easy path.
|
|
|
|
### 2. Metadata (P0/P1)
|
|
```
|
|
DValue file_stat(String path) // { exists, size, mtime, ctime, mode, is_dir, is_file, is_symlink }
|
|
DValue dir_list(String path) // [ { name, type, size, mtime } ... ]; optional glob/recursive flags
|
|
```
|
|
(`file_mtime` stays; `dir_list` is the richer `ls` so callers don't N+1 `file_stat`.)
|
|
|
|
### 3. Structural ops (P1)
|
|
```
|
|
bool file_rename(String from, String to) // atomic replace; enables write-temp-then-rename
|
|
bool file_copy(String from, String to) // host-side, preserves mode
|
|
bool file_truncate(String path, u64 size) // (or on a handle)
|
|
bool dir_remove(String path, bool recursive)
|
|
```
|
|
|
|
### 4. Niceties (P2)
|
|
```
|
|
String file_temp(String prefix) // race-free temp file under a write root (mkstemp)
|
|
bool file_chmod(String path, u32 mode)
|
|
bool file_symlink(String target, String linkpath); String file_readlink(String path)
|
|
// durability: file_put_contents/file_write gain an optional fsync flag/variant
|
|
```
|
|
|
|
### 5. Outbound HTTP (with TLS) (high value)
|
|
`socket_*` is raw TCP only — no secure outbound. Add a real client:
|
|
```
|
|
DValue http_request(DValue req) // req: { method, url, headers{}, body, timeout_ms, follow_redirects }
|
|
// -> { status, headers{}, body, error } (TLS/https supported)
|
|
u64 http_request_async(DValue req) // -> job_id (see Async jobs)
|
|
```
|
|
|
|
### 6. Crypto (extended) (high value)
|
|
`hash.h` has sha1 + rng only.
|
|
```
|
|
String sha256(String data); String sha256_hex(String data)
|
|
String hmac_sha256(String key, String data) // + _hex
|
|
String base64_encode(String data); String base64_decode(String b64)
|
|
String random_bytes(u64 n) // CSPRNG
|
|
bool crypto_equal(String a, String b) // constant-time compare
|
|
```
|
|
|
|
### 7. Shell (extended + background) (high value)
|
|
Current `shell_exec` is blocking + output-only.
|
|
```
|
|
DValue shell_exec(DValue spec) // spec: { cmd, stdin, env{}, timeout_ms } -> { exit_code, stdout, stderr, timed_out }
|
|
u64 shell_spawn(DValue spec) // background -> job_id; non-blocking; request returns immediately
|
|
```
|
|
(Keep a simple `shell_exec(String)` convenience overload returning stdout.)
|
|
|
|
### 8. Async jobs (the unifying "check back later" primitive)
|
|
Host-side job registry, survives the spawning request, reachable from any later
|
|
request/worker by id.
|
|
```
|
|
DValue job_status(u64 job_id) // { state: pending|running|done|failed, ... }
|
|
DValue job_result(u64 job_id) // blocks up to a small default; { done, ... , result }
|
|
DValue job_await(u64 job_id, u64 timeout_ms) // bounded wait; returns even if not done (state=running)
|
|
bool job_cancel(u64 job_id)
|
|
```
|
|
`shell_spawn` and `http_request_async` produce job ids consumed here. Jobs have a
|
|
TTL and are reaped; results are retrievable until collected or TTL expires.
|
|
|
|
## Explicitly OUT of scope (declined)
|
|
- DNS resolution API, UDP sockets, `setenv` / environment mutation.
|
|
|
|
## Future (design influences now, NOT implemented here)
|
|
- **Configurable trans-membrane blocklist.** A server config will let sysadmins
|
|
disable specific hostcalls by name (e.g. turn off `shell_exec`, `http_request`)
|
|
to harden a deployment. This is WHY principle 4 (one capability per named
|
|
hostcall) matters — keep the granularity clean so each is independently
|
|
gateable. A blocked call should return a clear "disabled by policy" error.
|
|
- **Native app extensions.** Longer term: let app developers ship their own
|
|
native compiled modules (`.so` living in the site tree) that units can call
|
|
across the membrane — a per-app plugin mechanism. Out of scope now, but the
|
|
hostcall/membrane shape should not preclude a future generic
|
|
"call host extension" dispatch.
|
|
|
|
## Implementation notes
|
|
- Each call = a discrete `uce_host_*` hostcall: add to `src/wasm/core_hostcalls.syms`,
|
|
register in `src/wasm/worker.cpp`, declare/implement the wasm-core wrapper in
|
|
`src/lib/sys.cpp` (+ header) mirroring existing hostcalls (sized-buffer for
|
|
bytes, sized-DValue/UCEB for structured returns).
|
|
- Reuse native impls where they exist; policy-gate every path.
|
|
- Each group lands suite-gated (`run_cli_tests --include-wasm-kill`, 0 failed) on
|
|
the build host; add tests + doc pages per new function (function pages, and
|
|
method/`2_` pages where a class gains methods).
|