diff --git a/PROTOCOL.md b/PROTOCOL.md index 144a48b..3a82b93 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -64,6 +64,7 @@ The world folder is the foundation of every instance's filesystem: | Path | Access | Contents | | --- | --- | --- | | `/` | read-write by every actor | the whole world folder | +| `/self` | read-write | alias for the actor's own folder (`public/`, `data/`, `source/`, etc.) | | `/data` | read-write | alias for the actor's state folder | | `/public` | read-write | alias for the actor's published files | | `/actor` | read-write by every actor | the `actor/` folder of the world | @@ -75,8 +76,9 @@ Creating a folder in the world makes it visible to running actors immediately: New worlds include an empty `/config` for global configuration by convention; existing worlds can simply create it. Keep secrets out of global configuration. -Candidate instances see the same writable world root. Their `/public` and -`/data` aliases still point to scratch copies, and network access remains off; +Candidate instances see the same writable world root. Their `/self` alias +points to the scratch actor folder; `/public` and `/data` still point to its +corresponding children, and network access remains off; absolute world paths are not isolated from the live world. `/actor//` holds everything about an actor: `actor.wasm`, `actor.json` @@ -146,9 +148,9 @@ the request: the request's `type` in past tense (`spawned`, `upgraded`, **Headless.** `sausage run --headless [dir]` (or `sausage --headless`, or `SAUSAGE_HEADLESS=1`) turns the console input off: the host does not read stdin, so end of input (`/dev/null`) does not stop the world. `nix run .#loop -- --headless ` passes it through the loop. It runs until SIGTERM or SIGINT (stops every actor, exit code 0) or a broker `exit` request (exit code 75, as above). Without the flag, EOF on stdin or `/quit` ends the host. -Paths are absolute world paths, or the sender's `/public/...` or `/data/...` -aliases. Native senders (such as the console) can also supply absolute host -paths; apart from the three existing aliases (`/actor`, `/public`, `/data`), +Paths are absolute world paths, or the sender's `/self/...`, `/public/...` or +`/data/...` aliases. Native senders (such as the console) can also supply +absolute host paths; apart from `/actor` and these aliases, these retain their host meaning. **Taps.** After `tap`, the sender gets a copy of every message that the diff --git a/cli/src/broker.rs b/cli/src/broker.rs index dc26278..f8587be 100644 --- a/cli/src/broker.rs +++ b/cli/src/broker.rs @@ -257,6 +257,7 @@ impl Broker { mounts: vec![ Mount { guest: "/".into(), host: self.world.root.clone(), writable: true }, Mount { guest: "/actor".into(), host: self.world.actors_root(), writable: true }, + Mount { guest: "/self".into(), host: own.clone(), writable: true }, Mount { guest: "/public".into(), host: own.join("public"), writable: true }, Mount { guest: "/data".into(), host: own.join("data"), writable: true }, ], diff --git a/cli/src/world.rs b/cli/src/world.rs index d1e9fdd..6035a7e 100644 --- a/cli/src/world.rs +++ b/cli/src/world.rs @@ -1,6 +1,7 @@ //! On-disk layout of a Sausage world. The folder is the source of truth: //! `sausage run` boots every actor that it finds in `actor/`, and then -//! watches the folder (`watch.rs`). +//! watches the folder (`watch.rs`). `/self` aliases the running actor's folder; +//! `/public` and `/data` remain aliases for its `public/` and `data/`. //! //! ```text //! / mounted at /, readable and writable by every actor @@ -194,7 +195,8 @@ impl World { /// Map a path in an actor's namespace to a host path. /// /// - Absolute paths are relative to the world root. - /// - `/public/...` and `/data/...` alias `sender`'s folders. + /// - `/self/...` aliases `sender`'s folder; `/public/...` and `/data/...` + /// alias its corresponding children. /// - Native senders (the console) can also give absolute host paths. pub fn resolve(&self, sender: &str, path: &str) -> Result { let under = |base: PathBuf, rest: &str| -> Result { @@ -214,6 +216,9 @@ impl World { if let Some(rest) = prefixed("/actor") { return under(self.actors_root(), rest); } + if let Some(rest) = prefixed("/self") { + return under(self.actor_dir(sender), rest); + } if let Some(rest) = prefixed("/public") { return under(self.public_dir(sender), rest); } diff --git a/cli/tests/host.rs b/cli/tests/host.rs index b75672e..f32132e 100644 --- a/cli/tests/host.rs +++ b/cli/tests/host.rs @@ -220,6 +220,8 @@ async fn actors_can_read_and_write_other_actor_data() { async fn world_root_is_shared_writable_and_survives_restart() { let (world, host, mut mailbox) = filesystem_world("world-root").await; assert!(world.root.join("config").is_dir()); + ask_type(&host, &mut mailbox, "writer", json!({"type": "write", "path": "/self/data/x", "text": "self-alias"}), "ok").await; + assert_eq!(ask_type(&host, &mut mailbox, "writer", json!({"type": "read", "path": "/data/x"}), "content").await.body["text"], "self-alias"); let file = "/config/x"; ask_type(&host, &mut mailbox, "writer", json!({"type": "write", "path": file, "text": "first"}), "ok").await; let read = json!({"type": "read", "path": file});