release: public alpha 0.6.0 baseline
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
# Extension Host
|
||||
|
||||
Status: signed-release Host and local Edge routing integration implemented.
|
||||
|
||||
The Edge supports `in-process`, `prefer-host`, and `host-only` execution modes.
|
||||
The production default remains `in-process`; the Host is not installed or
|
||||
enabled on production host.
|
||||
|
||||
## Process Model
|
||||
|
||||
One host process:
|
||||
|
||||
1. receives an explicit verified release directory;
|
||||
2. re-verifies installed release content, record, and publisher signature;
|
||||
3. checks the publisher against an explicit OpenSSH allowed-signers file;
|
||||
4. re-validates manifest and source policy;
|
||||
5. loads one plugin entrypoint;
|
||||
6. freezes declared command registration;
|
||||
7. reads one bounded JSON request from stdin;
|
||||
8. invokes one command;
|
||||
9. exchanges bounded service requests with the Kernel when declared;
|
||||
10. writes one final JSON response to stdout;
|
||||
11. exits.
|
||||
|
||||
Plugin stdout is redirected away from the protocol stream.
|
||||
Plugin stdout and stderr are bounded before reaching the parent pipe.
|
||||
|
||||
## Request
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "invoke_command",
|
||||
"command": "/hello",
|
||||
"arguments": "world",
|
||||
"event": {
|
||||
"version": 1,
|
||||
"event_id": "event-1",
|
||||
"observed_at": "2026-10-14T10:00:00+08:00",
|
||||
"sender": "JWeBot",
|
||||
"text": "@JWeBot /hello world",
|
||||
"confidence": 1.0,
|
||||
"sender_confidence": 1.0,
|
||||
"body_confidence": 1.0,
|
||||
"side": "incoming",
|
||||
"mention_bot": true,
|
||||
"group_fingerprint": "opaque-fingerprint",
|
||||
"source": "rapidocr-local"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The request contains no delivery target. The group fingerprint remains an
|
||||
opaque safety binding for the Kernel.
|
||||
|
||||
## Response
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"api_version": "1",
|
||||
"plugin_id": "hello-command",
|
||||
"result": {
|
||||
"api_version": "1",
|
||||
"reply": {"kind": "text", "text": "hello JWeBot world"},
|
||||
"facts": [],
|
||||
"metadata": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Errors return a short error class and do not include plugin exception text.
|
||||
|
||||
Declared service calls use an intermediate JSON-line exchange:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "service_request",
|
||||
"call_id": "1",
|
||||
"profile": "coin",
|
||||
"operation": "execute",
|
||||
"payload": {"command": "/翻硬币"}
|
||||
}
|
||||
```
|
||||
|
||||
The parent validates the profile, performs the Kernel-owned operation, and
|
||||
returns a matching `service_result`. Calls, message sizes, and total wall time
|
||||
are bounded. An undeclared profile, mismatched call ID, malformed result, or
|
||||
unavailable broker fails closed.
|
||||
|
||||
## Context Capabilities
|
||||
|
||||
The prototype provides:
|
||||
|
||||
- manifest-bound command registration;
|
||||
- declared, bounded Kernel service profiles;
|
||||
- bounded fact collection;
|
||||
- hashed logger messages with field names only.
|
||||
|
||||
The following brokers intentionally fail closed until implemented:
|
||||
|
||||
- storage;
|
||||
- approved HTTP;
|
||||
- scheduler.
|
||||
|
||||
No GUI sender, group selector, raw ledger, environment, shell, SSH, Docker, or
|
||||
arbitrary network client is supplied.
|
||||
|
||||
## Environment Handling
|
||||
|
||||
Before loading extension code, the CLI removes environment variables except
|
||||
`LANG`, `LC_ALL`, and `TZ`.
|
||||
|
||||
This reduces accidental secret exposure but is not a security sandbox.
|
||||
The parent interpreter also sets `PYTHONDONTWRITEBYTECODE=1`, and the loader
|
||||
disables bytecode writes while importing a plugin, so verified release
|
||||
directories cannot acquire unrecorded `__pycache__` files.
|
||||
|
||||
## Required Production Isolation
|
||||
|
||||
Before Edge integration, the host still needs an OS-enforced launcher with:
|
||||
|
||||
- dedicated dynamic or unprivileged identity;
|
||||
- read-only verified release directory;
|
||||
- private writable state directory;
|
||||
- no inherited secrets;
|
||||
- no network by default;
|
||||
- restricted address families;
|
||||
- no new privileges and empty capability set;
|
||||
- process, memory, CPU, and wall-time limits;
|
||||
- parent-enforced timeout and kill;
|
||||
- fixed Unix-socket or pipe protocol.
|
||||
|
||||
Static source checks and Python-level context restrictions do not replace these
|
||||
controls.
|
||||
|
||||
Repository templates:
|
||||
|
||||
```text
|
||||
deploy/systemd/system/jwebot-extension-host@.service
|
||||
deploy/systemd/system/jwebot-extension-host@.socket
|
||||
```
|
||||
|
||||
They specify a fixed unprivileged `jwebot-ext` identity, private AF_UNIX-only
|
||||
network namespace, empty capabilities, read-only system and release paths,
|
||||
private devices/tmp, syscall filtering, and CPU/memory/process/runtime limits.
|
||||
The service refuses to start unless
|
||||
`/etc/jwebot-extension-host/allowed_signers` is readable and passes that trust
|
||||
file to the Host. No private signing key belongs on the target.
|
||||
|
||||
The templates are not installed or enabled. A later provisioning phase must
|
||||
create the service identity, install the Host virtual environment, prepare
|
||||
release ownership and public-key trust file, run `systemd-analyze verify`, and
|
||||
test the socket protocol on the target Debian version.
|
||||
|
||||
## Parent Client
|
||||
|
||||
`jwebot_kernel.ExtensionHostClient` starts one Host in a new process group with:
|
||||
|
||||
- an explicit verified release directory;
|
||||
- `python -I` by default;
|
||||
- only `LANG`, `LC_ALL`, and `TZ` inherited;
|
||||
- closed unrelated file descriptors;
|
||||
- bounded request/response/stderr sizes;
|
||||
- a parent-enforced wall-time timeout;
|
||||
- process-group kill on timeout.
|
||||
|
||||
The parent discards Host stderr from protocol results and returns generic
|
||||
transport errors. It services only reviewed broker callbacks supplied by the
|
||||
Kernel.
|
||||
|
||||
At Edge startup, signed manifests are loaded from `<state-root>/<plugin-id>/current`,
|
||||
checked against the fixed ownership inventory, registered into one catalog,
|
||||
and frozen. Host health responses must exactly match the signed author,
|
||||
commands, service profiles, plugin ID, and API version.
|
||||
|
||||
`prefer-host` may use the in-process adapter only when a signed release is
|
||||
missing or its Host fails startup health. Once a Host invocation starts, the
|
||||
Kernel never replays that command through another path because the broker may
|
||||
already have committed state. `host-only` requires every reviewed plugin to be
|
||||
signed, installed, and healthy before Edge startup succeeds.
|
||||
|
||||
`jwebot_kernel.UnixExtensionHostClient` matches the systemd socket template. It
|
||||
uses the same bounded JSON-line service exchange and applies one end-to-end
|
||||
socket deadline. Socket absence, timeout, malformed JSON, service protocol
|
||||
violations, and Host error status all fail closed.
|
||||
Reference in New Issue
Block a user