# wip-fs-server `wip-fs-server` projects one configured Linux filesystem directory onto WIP Worldspace `/` at request time. It does not register the filesystem contents or keep one object instance per entry. It uses `wip-host` for publication and typed operation dispatch and uses the standard `wip-https` routes, JSON codecs, envelopes, batching, and HTTP status mapping. ## Security model - The effective root is opened once as a directory capability. Root path components must be real directories (symlink components are rejected), and the displayed effective root is read back from that same pinned descriptor. Every request is resolved segment-by-segment with directory-relative, `O_NOFOLLOW` handles. - Symlinks are projected as links and are never followed. Their stored target is available only through the `SymlinkInspect.read_link` application operation. - Regular files, directories, symlinks, and unsupported entry types have distinct public descriptions and capability sets. - WIP names are Unicode. Linux names that are not valid UTF-8 are deliberately not published; valid UTF-8 names map byte-for-byte and therefore cannot collide. Unicode paths containing backend-invalid NUL bytes are likewise treated as unpublished rather than as backend failures. - No object `ref` is issued. This avoids claiming representation identity for hard links or path-specific names. Regular files and symlinks receive an opaque validator containing filesystem identity plus protected metadata; replacing a path therefore does not preserve its validator. - Protocol metadata and errors never contain the backend absolute path. Startup prints only the explicitly configured effective root and security-sensitive modes. - The built-in listener is cleartext and loopback-only. Production deployments must pass `--behind-tls-proxy` and terminate HTTPS at a trusted reverse proxy. Plain development HTTP requires the explicit `--development-http` flag. The implementation currently targets Linux because it uses `O_PATH`, directory-relative descriptor traversal, and `/proc/self/fd` to reopen the same pinned backend object without resolving the untrusted Worldspace path twice. ## Run Read-only behind a TLS-terminating reverse proxy: ```console cargo run -p wip-fs-server -- \ --root /srv/public-files \ --listen 127.0.0.1:8443 \ --base-path /wip \ --behind-tls-proxy ``` Local development with the write capability deliberately enabled: ```console cargo run -p wip-fs-server -- \ --root ./example-files \ --listen 127.0.0.1:8080 \ --base-path /wip \ --development-http \ --read-write ``` The server reports the effective root, `ReadOnly`/`ReadWrite` mode, transport mode, endpoint, and actual listen address. It does not enumerate the root. ## Standard interactions The following examples use development HTTP for readability. The JSON is the standard `wip-https` profile; validators and operation `Bytes` are base64 body fields, never `ETag` or `If-Match` headers. Fetch root: ```console curl -sS -X POST http://127.0.0.1:8080/wip/v1/fetch \ -H 'Content-Type: application/json' -H 'Accept: application/json' \ --data '{"path":"/"}' ``` Fetch a bounded tree (the root counts toward `limit`): ```console curl -sS -X POST http://127.0.0.1:8080/wip/v1/fetch_tree \ -H 'Content-Type: application/json' -H 'Accept: application/json' \ --data '{"path":"/","depth":2,"limit":100}' ``` A regular-file fetch returns the opaque read interface reference and an object validator. Retrieve that descriptor with: ```console curl -sS -X POST http://127.0.0.1:8080/wip/v1/fetch_interface \ -H 'Content-Type: application/json' -H 'Accept: application/json' \ --data '{"interface":""}' ``` Then call a bounded range read: ```console curl -sS -X POST http://127.0.0.1:8080/wip/v1/call_operation \ -H 'Content-Type: application/json' -H 'Accept: application/json' \ --data '{ "target":{"path":"/notes.txt","validator":""}, "interface":{"reference":""}, "operation":"read", "arguments":{"offset":0,"length":4096} }' ``` In `--read-write` mode a second regular-file interface declares `replace`. Writes require the current object validator and return the post-write validator: ```console curl -sS -X POST http://127.0.0.1:8080/wip/v1/call_operation \ -H 'Content-Type: application/json' -H 'Accept: application/json' \ --data '{ "target":{"path":"/notes.txt","validator":""}, "interface":{"reference":""}, "operation":"replace", "arguments":{"contents":"dXBkYXRlZA=="} }' ``` `fetch`, `fetch_tree`, and `fetch_interface` can also be combined through the standard `POST /wip/v1/batch` route. `call_operation` is never batchable. ## Resource and failure behavior `ResourceLimits` bounds request/response bytes, HTTP request-head bytes, JSON nesting, batch items, file read/write bytes, tree depth/node count, pinned handles, and active connections/requests. One absolute request deadline covers header and body intake, dispatch, cooperative filesystem work, and response transmission; stalled clients are closed and cannot occupy a worker beyond that bound. Workers accept connections directly, so the listener never moves more than the configured concurrency into active userspace service. Advisory file-lock acquisition uses a nonblocking loop and checks the same deadline/cancellation rather than allowing a contended lock to stall a worker. Tree traversal is breadth-first and opens only requested entries. Once a limit or depth boundary is reached it reads only far enough to prove whether an additional visible child exists, so `truncated` is true only when something was actually omitted. I/O failure and cancellation remain errors, not clean exhaustion. Whole-file replacement compares the request validator against the identity and state of the same pinned file handle used for the write. Failures before mutation carry a `NotCommitted` guarantee and map to `Internal` or `ResourceLimitExceeded`; a failure after truncation/write begins maps to `OperationOutcomeUnknown`. The server and Clients do not automatically retry side-effecting operations.