Add 9P server design doc (draft)

Documents design options for:
- Section hierarchy (nested vs flat)
- Property granularity (per-file vs structured)
- Change notification
- Write semantics
- Agenda/query views

Needs decisions before implementation.
This commit is contained in:
Levi Neely 2026-09-30 10:05:18 +02:00
parent 7e24a9cf02
commit 030ac3beeb
1 changed files with 270 additions and 0 deletions

270
doc/9P_DESIGN.md Normal file
View File

@ -0,0 +1,270 @@
# Orkmode 9P Server Design
Status: **Draft — needs decisions**
## Goal
A 9P server that exposes org documents so any frontend (CLI, KDE app, web) can read/write by simple file operations. No client library needed.
## Core Questions
### 1. Section Hierarchy
Org sections nest: `* A` contains `** B` contains `*** C`.
**Option A: Nested directories**
```
sections/
└── a/
├── title
├── todo
└── children/
└── b/
├── title
└── children/
└── c/
```
- Pro: Reflects actual structure
- Con: Deep paths (`sections/a/children/b/children/c/title`), awkward to enumerate all
**Option B: Flat with parent refs**
```
sections/
├── idx # a\t1\tTitle A\tTODO\t\t(parent empty = root)
│ # b\t2\tTitle B\t\ta (parent = a)
│ # c\t3\tTitle C\t\tb (parent = b)
├── a/
│ ├── title
│ ├── parent # (empty)
│ └── children # b
├── b/
│ ├── title
│ ├── parent # a
│ └── children # c
└── c/
├── title
├── parent # b
└── children # (empty)
```
- Pro: Easy to list all sections, reconstruct tree client-side
- Con: Hierarchy implicit, extra `parent`/`children` files
**Option C: Hierarchical IDs**
```
sections/
├── idx # 1\tTitle A\tTODO
│ # 1.1\tTitle B
│ # 1.1.1\tTitle C
├── 1/
│ └── title
├── 1.1/
│ └── title
└── 1.1.1/
└── title
```
- Pro: ID encodes position, no extra files
- Con: IDs change when sections move, not stable for references
**Leaning toward:** Option B (flat + parent refs). Stable IDs (from CUSTOM_ID or hash), easy enumeration.
---
### 2. Property Granularity
How to expose section attributes?
**Option A: One file per property**
```
sections/review-pr/
├── title # Review PR
├── todo # TODO
├── priority # A
├── tags # work:urgent
├── properties # CUSTOM_ID=review-pr\nEFFORT=2h
├── planning # DEADLINE: <2025-01-20 Mon>
└── body # The actual content...
```
- Pro: `echo "DONE" > todo` just works, Unix composable
- Con: Many small files, multiple reads to get full section
**Option B: Single structured file per section**
```
sections/review-pr # title: Review PR
# todo: TODO
# priority: A
# tags: work urgent
# deadline: 2025-01-20
# body:
# The actual content...
```
- Pro: One read gets everything
- Con: Parsing needed, writes replace whole thing
**Option C: Both**
```
sections/review-pr/
├── data # Structured (all properties)
├── title # Just title (convenience)
├── todo # Just todo (convenience)
└── raw # Original org text for this section
```
- Pro: Flexible — use what fits
- Con: Redundancy, sync complexity
**Leaning toward:** Option A (one file per property). Matches Unix philosophy. Reads are cheap.
---
### 3. Change Notification
How does a GUI know when to refresh?
**Option A: No notification (poll)**
- Client re-reads periodically or on user action
- Simple, works everywhere
- Latency for external changes
**Option B: Blocking read (statewait pattern)**
- `cat sections/idx.wait` blocks until change, returns new content
- Client loops: read → update UI → read again
- Complex server state, client must handle reconnects
**Option C: External mechanism**
- Server writes to a Unix socket / named pipe on change
- Or: client uses inotify on .org files directly
- Separates concerns
**Option D: Skip for v1**
- CLI doesn't need it
- GUI can poll or watch .org files with inotify
- Add later if needed
**Leaning toward:** Option D for now. Don't over-engineer before we have a GUI.
---
### 4. Write Semantics
How do writes flow back to .org files?
**Option A: Direct property writes**
```bash
echo "DONE" > sections/review-pr/todo
# Server rewrites work.org with updated TODO state
```
- Pro: Intuitive
- Con: Partial writes tricky (tags append vs replace?)
**Option B: Command-based writes**
```bash
echo "todo DONE" > sections/review-pr/ctl
echo "tag +urgent" > sections/review-pr/ctl
echo "tag -work" > sections/review-pr/ctl
```
- Pro: Clear semantics, atomic operations
- Con: Less intuitive than direct file writes
**Option C: Document-level raw write**
```bash
# Edit raw org content
cat docs/work.org/raw > /tmp/edit.org
vim /tmp/edit.org
cat /tmp/edit.org > docs/work.org/raw
# Server reparses and updates index
```
- Pro: Full control, no semantic mismatch
- Con: Client does all the work
**Leaning toward:** Option A for simple cases (todo, priority), Option B (ctl) for complex ops (tag add/remove). Option C always available as escape hatch.
---
### 5. Agenda / Query Views
How to handle cross-document queries?
**Option A: Computed directories**
```
agenda/
├── today # Sections with deadline/scheduled today
├── week # This week
├── todos # All open TODOs
└── tags/
└── {tag} # Sections with this tag
```
- Reads compute result on demand
- Simple, no index maintenance
**Option B: Search file (rdwr)**
```
echo "todo:TODO tag:work" > search
cat search
# Returns matching section IDs
```
- More flexible queries
- Single entry point
**Option C: Both**
- Common queries as directories
- `search` for custom queries
**Leaning toward:** Start with Option A (fixed views). Add search later.
---
## Proposed Structure (v1)
```
/ork/
├── ctl # Commands: reload, quit
├── docs/
│ ├── idx # doc\tpath\ttitle\ttodos\tdone (per line)
│ └── {doc}/
│ ├── raw # Full .org content (r/w)
│ ├── meta # title, author, etc. (read-only)
│ └── sections/
│ ├── idx # id\tlevel\ttitle\ttodo\tpriority\ttags\tparent
│ └── {id}/
│ ├── title
│ ├── todo
│ ├── priority
│ ├── tags
│ ├── planning
│ ├── properties
│ ├── body
│ ├── parent
│ ├── children
│ └── ctl # Commands: todo X, tag +x, tag -x
├── agenda/
│ ├── today
│ ├── week
│ └── todos
└── capture # Write "* TODO text" to append to inbox
```
## Open Questions
1. **ID stability** — If CUSTOM_ID doesn't exist, generate from title hash? What if title changes?
2. **Concurrency** — Multiple writers (emacs + orkmode)? Last-write-wins, or detect conflicts?
3. **Large files** — Stream body content, or load all in memory?
4. **Symbolic links** — Should `agenda/today` entries be symlinks to actual section dirs?
5. **Error handling** — Write fails (invalid todo keyword) — return error how? (9P has Rerror)
---
## Implementation Plan
1. **ork-server crate** — 9P server using `nine` crate
2. **VirtFS layer** — Map namespace to org-parser operations
3. **File watcher** — Detect external .org changes, reparse
4. **CLI update** — `ork` talks to server instead of parsing directly (optional)
## References
- [nine crate](https://crates.io/crates/nine) — Rust 9P2000
- [9P protocol](http://9p.io/sys/man/5/INDEX.html) — Plan 9 manual
- ~/src/ollie — Working 9P implementation (Go)