Add armed plugin-originated file watches across VFS mutations

Register core.watch before its armed notification, coalesce later changes
without losing post-notification writes, and clean up on caller death.
Observe mounted layers and open file handles, including descriptor/stream
writes, truncation, both rename paths and deletion. Track cancelled
mutations through process idle and retain visible paths across renames.
Document the protocol, expose native core discovery, and exercise watches
through SDK callbacks and raw WASI filesystem operations.

Card: SAU-359
This commit is contained in:
SAU-359 implementer
2026-10-30 20:16:20 +00:00
parent c04baee67c
commit ff6fc62f05
16 changed files with 946 additions and 15 deletions
+26
View File
@@ -219,6 +219,7 @@ these areas fails.
/proc/
core/
PROTOCOL.md this file
discover.json core.rpc.discover document
graph.json plugins, imports, and the result of each check
graph.dot the same graph for Graphviz
locks.json held locks and waiting processes
@@ -246,6 +247,29 @@ Rules:
Principle: **inspect through files, act through calls.**
### File watching
`core.watch {"path": "/state/session/mailbox"}` stays open for the caller
process's lifetime. It needs a request ID and a running plugin caller.
Its first `core.notify` is `"armed"`, with empty params. Only then scan the
files. Writes before arming are visible in that scan; writes after arming
produce `"changed"` notifications (also with empty params). Rescan after
each notification; files, not event counts, are the truth.
Notifications follow successful plugin-originated file mutations: creation,
truncation, descriptor/stream writes, rename (both source and destination),
and deletion, including mounted `/run` and `/self` files. Paths are normalized
in the virtual namespace; `/self` means the caller's `/proc/<pid>` folder.
A watch matches itself and descendants, not sibling prefixes. Moving or
removing an ancestor also invalidates it. Paths need not exist when armed.
Open file writes follow plugin renames; unlinked/replaced handles no longer
name visible files. External host writes are outside this contract.
Changes may coalesce, including a change already visible in the initial
scan, but a change after the last notification always produces another.
Delivery holds no filesystem locks. The watch ends with its caller; there
is no cancellation API or polling/retry loop.
## 5. Endpoints
An endpoint is a JSON-RPC interface that a running process serves over TCP,
@@ -333,7 +357,9 @@ At start, the core starts each service in `/config/boot.yaml`, in order:
| Method | Params | Result |
| --- | --- | --- |
| `core.rpc.discover` | none | OpenRPC core API document (also `/proc/core/discover.json`) |
| `core.call` | `method`, `params`, `detach`? | the result of the method |
| `core.watch` | `path` | stays open; `armed`, then `changed` notifications (section 4) |
| `core.notify` | `name`, `params` | none (a notification) |
| `core.stop` | `pid` | `{}` when the process has ended |
| `core.kill` | `pid` | `{}` |