core: review fixes, init builds the agent, console without a terminal

Activation path (review T6R): a broken plugin or a stray file in
/plugins blocks no activation; candidates replace their own entries;
discovery has a 10 s deadline and its process is stopped after the
answer; an unchanged hash keeps plugin.prev.wasm; activations run in
their own task; the watcher is not recursive; no duplicate services;
rpc.retired waits for live processes of its version; reads and hashes
on blocking threads.

Guest I/O and per-call paths: a stdin write error is EOF, stdout and
stderr are drained before the status; stderr is lossy UTF-8 and goes
to the journal from its own reader; compiled components are freed;
shutdown starts no new processes; lock queues drop stopping processes;
compile before the admission gate; CommandPre per component;
core.unlock in order; null errors are successes; the journal thread
survives SQLite errors.

sausage init builds the agent with cargo.build; sausage without a
command creates ./world. The console reads plain lines without a
terminal. PROTOCOL.md and DECISIONS.md describe the decided rules. The
flake tests the whole workspace and checks the packaged cargo.build.
This commit is contained in:
2026-10-23 02:25:03 +01:00
parent 53b90a8ecf
commit d7b78ed0fd
27 changed files with 1800 additions and 352 deletions
+22 -9
View File
@@ -138,7 +138,8 @@ Rules:
children stop in parallel, so a whole tree takes 5 seconds at most.
- A detached process has no parent. Only `core.stop`, `core.kill`, an
upgrade (section 3.7), and the end of the host stop it.
- At host shutdown, the core stops every process in parallel.
- At host shutdown, the core starts no new processes, and it stops every
process in parallel.
Note: on Unix, children outlive their parent. Here, an attached child ends
with its parent. Only `detach` makes a process independent.
@@ -163,6 +164,11 @@ active (section 10.3):
During the grace period, an old process can call a method that a new
provider no longer has.
If the host stops before the last old process has ended, `rpc.retired` does
not run for that version. So a new version must also work with state that
was not migrated. Check for a migration marker, and migrate when it is
missing.
### 3.8 Environment and network
- `SAUSAGE_PID`: the process ID.
@@ -325,8 +331,8 @@ are JSON Schema Draft 7, as OpenRPC requires. Shared schemas are in
`components.schemas`, and `$ref`s point to `#/components/schemas/<Name>`.
Params are by name. The document can have these extension fields:
- `x-sausage-params` for each method: a `$ref` to the schema of the params
object.
- `x-sausage-params` for each method with params: a `$ref` to the schema of
the params object.
- `x-sausage-endpoints`: the endpoint interfaces by name, each with its
`methods`.
- `x-sausage-imports`: for each provider, the part of its document that the
@@ -337,8 +343,9 @@ The schema of params describes what the provider can read. The schema of a
result describes what the provider writes. Descriptions are part of the
interface: other agents read them to use it.
The core starts a process only to call `rpc.discover`. Do not do expensive
work at start.
The core starts a process only to call `rpc.discover`, and it stops that
process after the answer. Do not do expensive work at start. If no answer
comes within 10 seconds, the discovery fails.
### 10.2 Compatibility
@@ -366,11 +373,14 @@ imported method:
1. The core copies the file. All further steps use this copy, even if the
file changes.
2. It starts the copy and calls `rpc.discover`.
3. It waits until it has the documents of all plugins (section 10.4).
3. It gets the current documents of all plugins (section 10.4). A plugin
whose discovery fails is "unavailable". Its imports cannot be checked,
but it does not block the activation of other plugins.
4. It checks the imports of the new version against the current providers.
5. It checks each current consumer of the plugin against the new version.
6. If all checks pass, `plugin.wasm` becomes `plugin.prev.wasm`, and the copy
becomes `plugin.wasm`. Then the upgrade starts (section 3.7).
becomes `plugin.wasm`. Then the upgrade starts (section 3.7). If the copy
has the same hash as the current version, nothing changes.
If a check fails, nothing changes, and the error -32003 lists each problem,
with a counterexample where possible.
@@ -391,7 +401,8 @@ with a counterexample where possible.
- A file watcher on `/plugins/*/plugin.wasm` updates them, for example after
a copy by hand. `/proc/core/graph.json` then shows each broken import, and
the journal records it.
- An import of a plugin that does not exist is "missing".
- An import of a plugin that does not exist is "missing". An import of a
plugin whose discovery failed is "unavailable".
## 11. Building and tests
@@ -420,6 +431,8 @@ Tests:
and `--ignored`. `#[should_panic]` cannot work, because WASI cannot catch
a panic.
- Each test gets an empty temporary world as `/`, the package folder at its
path, and its own `/proc`, `/self`, and `/run`.
host path, and its own `/proc`, `/self`, and `/run`.
- The current folder of a test is `/`, not the package folder. Read fixture
files through `env!("CARGO_MANIFEST_DIR")`.
- The test runner answers `core.lock`, `core.unlock`, `core.notify`, and
`core.log`. A call to a plugin fails: a test has no other plugins.