<feed xmlns='http://www.w3.org/2005/Atom'>
<title>pardes.git/docs/9p.pdf, branch main</title>
<subtitle>Pardes</subtitle>
<id>https://git.0x4200.cafe/pardes.git/atom?h=main</id>
<link rel='self' href='https://git.0x4200.cafe/pardes.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/'/>
<updated>2026-08-28T01:09:08Z</updated>
<entry>
<title>build: pardes builds without the ESP32-P4 toolchain checkout beside it</title>
<updated>2026-08-28T01:09:08Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-27T19:42:15Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=98b62629795e24de19f535f1072b8a25dcf9f018'/>
<id>urn:sha1:98b62629795e24de19f535f1072b8a25dcf9f018</id>
<content type='text'>
`build.zig.zon` named `../05-zig-p4` as a path dependency, and `build.zig`
`@import`ed it inside `if (-Desp32p4-firmware)`. But `@import` in a build script
is resolved when the SCRIPT is compiled, not when the branch that needs it is
taken -- so naming the package at all meant anyone without that sibling checkout
could not build pardes AT ALL. Not the firmware: the terminal shell, the SDL
shell, the tests. `zig build` failed with

    build.zig:1073: error: no module named 'zig_p4' available within module 'root.@build'

from a line inside an `if` that was false.

Neither escape hatch works for a PATH dependency, and both were tried rather
than assumed. `.lazy = true` is about FETCHING; a path dep whose directory is
absent is generated as a package with no `build.zig` rather than one marked
unavailable, so `b.lazyImport` -- which exists for exactly this and is what the
standard library says is to `@import` what `lazyDependency` is to `dependency`
-- reaches a `@compileError` instead of returning null. Making it a fetched
dependency instead is not available either: the toolchain has no remote.

So the duplicate goes. That build tree's firmware block linked an image the
toolchain repository already knows how to link -- its own build.zig has
`-Dpardes`, `-Dapp=&lt;root&gt;` and `-Dpardes-obj=&lt;path&gt;`, and its comments record
having learned this same lesson from the other direction, where nesting pardes's
~30-package graph under it broke every build there. The object is the seam: it
crosses by PATH and never by package, and each repository builds what it owns
the pieces of.

  zig build -Dplatform=esp32p4                    # here, no toolchain needed
  zig build -Dpardes                              # there, the console image
  zig build -Dpardes -Dapp=&lt;pardes&gt;/src/esp32p4_9p.zig   # there, the 9P image

For the second and third to work with no module map, `src/board9p.zig` and the
9P firmware root now reach the codec by PATH instead of through a named `ninep`
module that only pardes's own build.zig knew to inject -- which is also why the
root moved from `src/esp32p4/nine.zig` up to `src/esp32p4_9p.zig`, beside
`src/esp32p4.zig`: a path import may not escape its module's own directory. Both
files are now self-contained, and `zig test src/board9p.zig` works with no flags.

`-Desp32p4-port`, `-Desp32p4-prof` and `-Desp32p4-cpu-mhz` go with the block. An
option this build cannot honour is worse than no option, because it accepts the
flag and then ignores it; all three are spelled the same way in the toolchain.

Verified by moving ../05-zig-p4 out of the way: `zig build`, `zig build
-Dplatform=esp32p4` and `zig build unit-test` all pass without it. With it back,
the toolchain still links both images -- console 812,720 B, 9P 88,096 B.

next-steps.txt gains the six features the 9P chain shipped.
</content>
</entry>
<entry>
<title>9p: serve the acme tree over 9P2000 on a unix socket, beside the mount</title>
<updated>2026-08-27T19:42:08Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-27T18:32:02Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=def843b2f59b867ee9b1d501f559f59fb335d4cc'/>
<id>urn:sha1:def843b2f59b867ee9b1d501f559f59fb335d4cc</id>
<content type='text'>
Step 4 of the 9P chain (docs/9p.typ 12.4, docs/registry.typ 9P-15/16/17/4/5).

src/9p.zig is a base 9P2000 codec and a SANS-IO server: it never touches a
descriptor, takes no allocator, starts no thread, and builds for
wasm32-freestanding and riscv32-freestanding. That is what lets the same code
serve a unix socket here and a UART on the board later.

  Server(comptime fs: type)   duck-typed on fs.Req/fs.Reply/fs.Reply.Attr, so it
                              never imports acmefs and acmefs never learns 9P
  init{ in, out, root }       the caller owns the buffers; msize is derived
  retry/next/reply            the three fs_service.Transport ops, by name
  push/output/wrote/hangup    bytes in, bytes out, partial writes supported

next() is a PUMP, not one-message-one-request: a 3-element Twalk is three
lookups, Topen|OTRUNC is a setattr then an open, Tversion is none at all.

Decisions that were open and are now taken, each recorded in the file:
  * qid.version is ALWAYS 0, which makes Linux set P9L_DIRECT and skip its
    cache -- the 9P equivalent of the FOPEN_DIRECT_IO fuse.zig relies on.
  * Every Rread is clamped to the client's count. An over-long one is a hard
    -EIO in Linux, not a truncation.
  * Rerror carries Linux's exact strerror text (registry 9P-4 option A), so a
    mount recovers the errno instead of ESERVERFAULT. Asserted as literals,
    because a typo there is 'Unknown error 526' on every mount.
  * `.` and `..` are resolved BY THE SERVER. Under FUSE the kernel does it
    and acmefs says so; 9P has no kernel, and forwarding `..` as a lookup
    would break every client that normalises a path.
  * Topen checks the perm bits itself. Under FUSE the kernel enforced them;
    over 9P nobody is above the server, and `errors` would have been readable.
  * Tcreate and Tremove are Rerror: `new/` creates a pane on WALK, so the
    capability exists and is not spelled Tcreate.

THE INTEGRATION BUG, which was not in the protocol: the daemon's push_fs_reply
sent every reply to the FUSE mount, whose park table has no 9P tag, so it
dropped it -- Tversion worked (no core involved) and Tattach hung forever. That
is exactly the 'no routing origin for the 9P descriptor' cell in the layering
table of docs/9p.typ. Session.fs_origin now carries the transport that asked.

Proved with plan9port against a live daemon serving BOTH transports at once:
9p ls / and /1, read index/ctl/tag, write /1/body, stat, a walk through
/1/../index, pane creation through `new/body`, and the two refusals arriving as
strings -- 'permission denied' and 'No such file or directory' -- confirmed on
the raw wire as Rerror text rather than numbers. A write over 9P reads back
through FUSE and a write through FUSE reads back over 9P.

msize 8192, 34,072 bytes per connection (Server 9,488 + in 8,192 + out 16,384,
out being two msize so that every reply is infallible), four connections.
zig build unit-test: 468 tests before, 503 after.
</content>
</entry>
<entry>
<title>docs: a 9P design note and a design registry to argue it in</title>
<updated>2026-08-27T18:28:00Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-27T17:27:05Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=3580ef4d7459035d82d38dbc4559cdede3c805ba'/>
<id>urn:sha1:3580ef4d7459035d82d38dbc4559cdede3c805ba</id>
<content type='text'>
</content>
</entry>
</feed>
