The daemon can't start on macOS: the new secure-local-state preflight (src/tendwire/local_state.py, introduced with the 0.7.0 hardening) hard-fails with no opt-out. I've root-caused it, confirmed macOS has the pieces for a portable fix, and started one — but there's a second, deeper issue I'd like maintainer guidance on before completing, since this is security-critical code.
Repro
$ tendwire daemon --db-path <path>/tendwire.db
tendwire.local_state.LocalStateError: secure local state requires supported POSIX filesystem operations # UNSUPPORTED_PLATFORM
Root cause (blocker 1 — fixable)
proc_fd_path and canonical_path_from_fd resolve an open directory fd back to a pathname via /proc/self/fd/<dir_fd>, gated on sys.platform.startswith("linux"). macOS has no procfs, so both raise UNSUPPORTED_PLATFORM (these feed the sqlite terminal preflight).
Notably, the capability preflight is fine on macOS — it already passes O_NOFOLLOW, O_DIRECTORY, dir_fd support for open/stat/mkdir/unlink/chmod/chown/link, and stat/chown follow_symlinks. The only Linux-specific dependency is the /proc fd→path resolution.
macOS/BSD have the exact portable equivalent: fcntl(fd, F_GETPATH) returns the descriptor's canonical pathname (verified round-trip on macOS 26.5: F_GETPATH(/tmp fd) -> /private/tmp, same inode).
Partial fix (offered)
Branch Micaxes/tendwire:macos-local-state (commit 28edb66, off current main) adds a _fd_directory_path() helper that uses F_GETPATH on non-Linux POSIX hosts, re-verified against the fd's inode exactly as the /proc path is. This clears the UNSUPPORTED_PLATFORM wall — the two functions resolve correctly and the daemon gets past that preflight.
Blocker 2 (needs your guidance)
With blocker 1 fixed, the daemon then fails ENTRY_CHANGED deeper in the strict path-walk. An isolated probe of proc_fd_path/canonical_path_from_fd on macOS passes the same_inode check, so the mismatch is elsewhere in the walk — I suspect an interaction between the O_NOFOLLOW component-by-component walk and F_GETPATH's canonicalization (possibly APFS firmlinks like /Users). Separately, tests/test_store.py has ~257 macOS failures, so there may be more Linux-specific assumptions in the secure-local-state model.
Since this is a security boundary, I didn't want to keep changing its guarantees blind. Happy to complete the macOS port (and the F_GETPATH change above) if you can point me at the intended invariants — especially whether the path-walk assumes Linux-only /proc-anchored resolution semantics that F_GETPATH can't fully reproduce.
Environment
- macOS 26.5.1 (arm64) · Python 3.14.3 · tendwire
v0.1.0rc5 (33fa90a)
- Context: driving the Herdres→Telegram bridge on macOS (a downstream vendors this engine). Prior macOS gaps (turn adapter) were resolved upstream in herdres #138.
The daemon can't start on macOS: the new secure-local-state preflight (
src/tendwire/local_state.py, introduced with the 0.7.0 hardening) hard-fails with no opt-out. I've root-caused it, confirmed macOS has the pieces for a portable fix, and started one — but there's a second, deeper issue I'd like maintainer guidance on before completing, since this is security-critical code.Repro
Root cause (blocker 1 — fixable)
proc_fd_pathandcanonical_path_from_fdresolve an open directory fd back to a pathname via/proc/self/fd/<dir_fd>, gated onsys.platform.startswith("linux"). macOS has no procfs, so both raiseUNSUPPORTED_PLATFORM(these feed the sqlite terminal preflight).Notably, the capability preflight is fine on macOS — it already passes
O_NOFOLLOW,O_DIRECTORY,dir_fdsupport for open/stat/mkdir/unlink/chmod/chown/link, andstat/chownfollow_symlinks. The only Linux-specific dependency is the/procfd→path resolution.macOS/BSD have the exact portable equivalent:
fcntl(fd, F_GETPATH)returns the descriptor's canonical pathname (verified round-trip on macOS 26.5:F_GETPATH(/tmp fd) -> /private/tmp, same inode).Partial fix (offered)
Branch
Micaxes/tendwire:macos-local-state(commit28edb66, off currentmain) adds a_fd_directory_path()helper that usesF_GETPATHon non-Linux POSIX hosts, re-verified against the fd's inode exactly as the/procpath is. This clears theUNSUPPORTED_PLATFORMwall — the two functions resolve correctly and the daemon gets past that preflight.Blocker 2 (needs your guidance)
With blocker 1 fixed, the daemon then fails
ENTRY_CHANGEDdeeper in the strict path-walk. An isolated probe ofproc_fd_path/canonical_path_from_fdon macOS passes thesame_inodecheck, so the mismatch is elsewhere in the walk — I suspect an interaction between theO_NOFOLLOWcomponent-by-component walk andF_GETPATH's canonicalization (possibly APFS firmlinks like/Users). Separately,tests/test_store.pyhas ~257 macOS failures, so there may be more Linux-specific assumptions in the secure-local-state model.Since this is a security boundary, I didn't want to keep changing its guarantees blind. Happy to complete the macOS port (and the F_GETPATH change above) if you can point me at the intended invariants — especially whether the path-walk assumes Linux-only
/proc-anchored resolution semantics thatF_GETPATHcan't fully reproduce.Environment
v0.1.0rc5(33fa90a)