docs: add project planning documents

This commit is contained in:
2026-04-28 23:59:45 -04:00
commit 8e6646499f
16 changed files with 2343 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
# ADR-001: Go Backend with Preact Frontend
## Status
Accepted
## Context
We need to choose a backend language/framework and frontend technology for a documentation platform with these requirements:
- Ultra-fast performance
- Minimal external dependencies
- No vendor lock-in
- Battle-tested security
- Server-side rendering for non-interactive pages
- Offline-capable web client
## Decision
**Backend**: Go 1.22+ with standard library plus minimal, well-audited packages
**Frontend**: Preact 10 with TypeScript, Vite build system
## Consequences
### Positive
- Go's standard library is comprehensive and security-audited
- Single static binary deployment — no runtime dependencies
- Preact is 10KB (vs 40KB+ React) with identical API
- Go's `html/template` provides safe SSR with auto-escaping
- TypeScript gives type safety without runtime overhead
- Both ecosystems have excellent supply-chain security (Go modules with checksums, npm audit)
### Negative
- Go template syntax is less expressive than JSX
- WebSocket sync logic must be implemented manually (no framework like Socket.io)
- Preact ecosystem smaller than React (mitigated by using mostly native APIs)
## Alternatives Considered
### Rust + Axum
- **Pros**: Memory safety guarantees, excellent performance
- **Cons**: Longer compile times, smaller talent pool, steeper learning curve for team
- **Rejected**: Go's pragmatic balance of safety and velocity better fits project timeline
### Deno Fresh
- **Pros**: Island architecture, native TypeScript, no build step
- **Cons**: Newer ecosystem, fewer audited libraries, Deno runtime less battle-tested than Go
- **Rejected**: Go's maturity and deployment simplicity preferred
### Next.js / React Server Components
- **Pros**: Mature ecosystem, built-in SSR
- **Cons**: Heavy bundle size, complex build system, RSC lock-in, many dependencies
- **Rejected**: Violates "minimal dependencies" and "no vendor lock-in" principles
## References
- Go Security Policy: https://go.dev/security
- Preact Size Comparison: https://bundlephobia.com/package/preact@10.19.3

View File

@@ -0,0 +1,81 @@
# ADR-002: SimpleSync Protocol
## Status
Accepted
## Context
Users need to edit markdown files on their local filesystem (using any editor) and have changes reflected on the web automatically. Multiple users should be able to work on different files simultaneously without conflicts. We need a sync mechanism simpler than git but robust enough for production use.
## Decision
Implement **SimpleSync**: a content-addressed, Merkle-tree-inspired sync protocol over WebSocket with the following characteristics:
1. **Content-addressed storage**: All files identified by SHA-256 hash
2. **File-level granularity**: Parallel editing of different files is conflict-free
3. **Automatic sync**: File watcher detects changes and pushes to server
4. **Three-way merge**: Only when same file edited concurrently
5. **No branches/commits**: Just current state + history log
## Protocol Flow
### Client → Server: Sync Request
```json
{
"type": "sync_request",
"client_root_hash": "abc123...",
"client_files": {
"getting-started.md": "hash1",
"api-reference.md": "hash2"
}
}
```
### Server → Client: Sync Response
```json
{
"type": "sync_response",
"server_root_hash": "def456...",
"missing_from_client": ["hash3", "hash4"],
"missing_from_server": ["hash5"],
"conflicts": ["getting-started.md"]
}
```
### Content Transfer
Files transferred by hash: `GET /content/{hash}` or WebSocket binary frames
## Consequences
### Positive
- Dramatically simpler mental model than git
- Automatic — no user commands needed
- Works with any text editor
- Immutable file store provides natural deduplication
- Easy to implement offline queue: just store hashes and content
### Negative
- No offline branching (by design)
- Conflict resolution UI must be built
- No rebase/squash history manipulation (intentional simplicity)
- Large binary files could bloat content store (mitigated by size limits)
## Alternatives Considered
### Git-based sync
- **Pros**: Mature, well-understood, existing tools
- **Cons**: Requires git on client, complex merge UX, repo-level locking
- **Rejected**: Too complex for notes; overkill for target use case
### CRDTs (Yjs, Automerge)
- **Pros**: Real-time collaborative editing, automatic conflict resolution
- **Cons**: Complex to implement correctly, memory overhead, designed for real-time not async
- **Rejected**: Requirement is async-only; CRDTs are overkill
### rsync / Unison
- **Pros**: Battle-tested file sync
- **Cons**: No concept of "versions" or "comments", no web integration
- **Rejected**: Doesn't meet collaboration requirements
## References
- Content-addressable storage: https://en.wikipedia.org/wiki/Content-addressable_storage
- Merkle trees: https://en.wikipedia.org/wiki/Merkle_tree
- WebSocket RFC: https://tools.ietf.org/html/rfc6455

View File

@@ -0,0 +1,67 @@
# ADR-003: SSR Strategy - Go Templates with Preact Hydration
## Status
Accepted
## Context
The application needs to serve document pages that are readable without JavaScript (for performance and accessibility), while providing rich interactivity (comments, editing, search) when JavaScript is available. We need a server-side rendering strategy that works with our Go backend and Preact frontend.
## Decision
**Strategy C**: Go's `html/template` renders initial HTML for all pages. Preact hydrates interactive components on the client.
### Implementation Details
1. **Server Rendering**:
- Go handlers query database for document content
- Markdown parsed to HTML server-side (Goldmark)
- `html/template` renders full page with document HTML embedded
- No JavaScript required for reading
2. **Client Hydration**:
- Preact components mount into designated DOM containers
- Comments, search, editor are Preact islands
- Static content remains server-rendered HTML
- `preact-iso` handles client-side navigation between pages
3. **Data Passing**:
- Initial state embedded as JSON in `<script type="application/json">` tags
- Preact reads initial state during hydration, avoids extra API calls
- CSP nonce applied to all inline scripts
## Consequences
### Positive
- First contentful paint is instant (pure HTML)
- SEO-friendly without extra complexity
- Graceful degradation: works without JS
- Go templates are auto-escaping XSS-safe
- No V8/JS runtime embedded in Go binary
- Smaller client bundle (only interactive components)
### Negative
- Some code duplication: markdown parsing logic in both Go and JS (for preview)
- Template syntax is verbose compared to JSX
- Hydration can cause flicker if not carefully managed
- Client state must reconcile with server-rendered HTML
## Alternatives Considered
### Go Templates Only (No Preact)
- **Pros**: Zero client JS, maximum performance
- **Cons**: No interactive features (comments, real-time sync, search)
- **Rejected**: Doesn't meet collaboration requirements
### Embedded JS Runtime (QuickJS/V8)
- **Pros**: True isomorphic rendering, JSX templates
- **Cons**: Adds C dependency, complex build, larger binary, security surface area
- **Rejected**: Violates minimal dependency principle
### Preact SSR via WASM
- **Pros**: Same templates on server and client
- **Cons**: WASM overhead, complex build pipeline, slower than Go templates
- **Rejected**: Go templates are faster and simpler
## References
- Go html/template security: https://pkg.go.dev/html/template
- Preact hydration: https://preactjs.com/guide/v10/server-side-rendering
- Islands Architecture: https://jasonformat.com/islands-architecture/

View File

@@ -0,0 +1,74 @@
# ADR-004: Authentication Strategy
## Status
Accepted
## Context
The platform needs secure authentication for both technical and non-technical users. Requirements include: primary passkey support, password fallback, session management, and resistance to common attacks (phishing, credential stuffing, session hijacking).
## Decision
**Passkeys as primary authentication method, Argon2id-hashed passwords as fallback.**
### Implementation
1. **Registration Flow**:
- User provides email
- System generates WebAuthn registration challenge
- Browser creates passkey ( Touch ID, Windows Hello, YubiKey)
- Server stores credential ID and public key
- Optional: user sets password fallback
2. **Login Flow (Passkey)**:
- User enters email
- Server returns WebAuthn assertion challenge
- Browser signs challenge with passkey
- Server verifies signature, creates session
3. **Login Flow (Password)**:
- User enters email + password
- Server verifies Argon2id hash (constant-time comparison)
- If valid, creates session
- Prompts to set up passkey (progressive enhancement)
4. **Session Management**:
- Signed cookies with 24-hour expiry
- Refresh tokens rotate on each use
- Sessions stored in database (revocable)
- HttpOnly, Secure, SameSite=Strict flags
## Consequences
### Positive
- Passkeys are phishing-resistant by design
- No password database to breach (if passkey-only)
- Argon2id is winner of Password Hashing Competition
- Session tokens are revocable server-side
- Progressive enhancement: passkey users never type passwords
### Negative
- Passkey support varies by browser/OS
- Users may lose access if they lose all devices (recovery flow needed)
- WebAuthn implementation complexity (mitigated by go-webauthn library)
- Backup codes or recovery email needed for account recovery
## Alternatives Considered
### OAuth 2.0 / SSO Only
- **Pros**: No password management, familiar UX
- **Cons**: Vendor lock-in to identity providers, privacy concerns, not self-hosted
- **Rejected**: Violates "no vendor lock-in" principle
### Passwords Only (with 2FA)
- **Pros**: Simple, universally supported
- **Cons**: Vulnerable to phishing, credential stuffing, SIM swapping
- **Rejected**: Passkeys are strictly superior
### Magic Links
- **Pros**: No passwords, simple UX
- **Cons**: Email dependency, slower, inbox security issues
- **Rejected**: Less secure than passkeys, poor for frequent login
## References
- WebAuthn spec: https://www.w3.org/TR/webauthn-2/
- go-webauthn library: https://github.com/go-webauthn/webauthn
- Argon2id RFC: https://datatracker.ietf.org/doc/html/rfc9106

View File

@@ -0,0 +1,68 @@
# ADR-005: Storage Strategy
## Status
Accepted
## Context
The system needs to store: structured data (users, documents, comments), document content, file attachments, and search indexes. Requirements include: zero external dependencies, backup simplicity, and resistance to data corruption.
## Decision
**libsql (SQLite) for structured data + content-addressed filesystem for files**
### Implementation
1. **Database (libsql/SQLite)**:
- Single file: `data/db.sqlite`
- WAL mode for concurrent reads during writes
- Embedded — no separate process needed
- Backup: `cp db.sqlite db.sqlite.backup` (atomic with WAL checkpoint)
2. **File Storage (Content-Addressed)**:
- Directory: `data/files/`
- Layout: `data/files/ab/cd/abcdef1234567890abcdef1234567890abcdef12`
- SHA-256 hash as filename (hex encoded)
- First two characters as subdirectory (for filesystem performance)
- Immutable: write-once, never modify
3. **Document Mapping**:
- Database stores: `document_path → current_hash`
- History table stores all previous hashes
- Attachment metadata stores original filename + content hash
## Consequences
### Positive
- SQLite is the most tested database in the world (billions of deployments)
- libsql adds replication option without changing the API
- Content-addressed files are naturally deduplicated
- Filesystem storage is trivial to backup (rsync, tar)
- No network dependencies, works offline
- Single directory contains all state — easy to migrate
### Negative
- SQLite has write concurrency limits (mitigated by WAL + short transactions)
- Filesystem storage doesn't scale horizontally without shared storage
- No built-in file expiration/garbage collection (must implement orphan cleanup)
- Large attachments can bloat filesystem (mitigated by quotas)
## Alternatives Considered
### PostgreSQL
- **Pros**: Excellent concurrency, rich feature set, horizontal scaling
- **Cons**: Requires separate process, more complex backup, network dependency
- **Rejected**: Overkill for anticipated scale; SQLite is simpler and faster for read-heavy workloads
### Object Storage (S3/MinIO)
- **Pros**: Unlimited scale, CDN-friendly
- **Cons**: Network dependency, vendor lock-in, complex local development
- **Rejected**: Violates zero-dependency and simple backup requirements
### SQLite for Everything (BLOBs)
- **Pros**: Single file for everything
- **Cons**: Large BLOBs slow down database, harder to serve directly, backup size bloat
- **Rejected**: Separation of concerns — database for metadata, filesystem for content
## References
- SQLite WAL mode: https://sqlite.org/wal.html
- Content-addressable storage: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
- libsql: https://github.com/tursodatabase/libsql

View File

@@ -0,0 +1,79 @@
# ADR-006: Email Integration
## Status
Accepted
## Context
The platform needs to send email notifications for: file changes (to watchers), new comments, conflict alerts, and digest summaries. It must also receive email replies and convert them to comments. Requirement: use Postmark email gateway with plain-text only.
## Decision
**Postmark HTTP API for outgoing email, Postmark inbound webhook for incoming email.**
### Implementation
1. **Outgoing Email**:
- Go adapter using `github.com/keighl/postmark`
- Plain-text templates (no HTML)
- Message-ID generated per email for threading
- Templates: notification, digest, conflict-alert, welcome
2. **Incoming Email**:
- Postmark inbound webhook POSTs to `/webhooks/email`
- Webhook payload parsed for: from, subject, body, in-reply-to
- Reply matched to comment thread via In-Reply-To header
- Body converted to plain text comment
- Authentication: webhook signature verification
3. **Email Format**:
```
From: MD Hub Secure <notifications@example.com>
To: user@example.com
Subject: [docs/getting-started.md] Comment from Alice
Message-ID: <comment-123@example.com>
In-Reply-To: <comment-122@example.com>
Alice commented on "Getting Started":
> This section needs clarification...
Reply to this email to add a comment.
View online: https://example.com/docs/getting-started#comment-123
```
## Consequences
### Positive
- Postmark has excellent deliverability reputation
- Plain-text emails are accessible and lightweight
- Email threading works in all clients
- No HTML parsing/rendering security risks
- Webhook integration is simple and reliable
### Negative
- Postmark is a paid service (though inexpensive)
- Requires DNS setup (SPF, DKIM) for deliverability
- Plain-text limits formatting options (intentional trade-off)
- Rate limits on Postmark API (mitigated by queue + retry)
## Alternatives Considered
### SMTP Direct Delivery
- **Pros**: No third-party dependency, full control
- **Cons**: Complex deliverability management, IP reputation, spam folder issues
- **Rejected**: Operational burden too high for small team
### SendGrid / Mailgun
- **Pros**: Similar features to Postmark
- **Cons**: More expensive at scale, complex APIs
- **Rejected**: Postmark has better reputation for transactional email
### No Email (In-App Only)
- **Pros**: Zero email complexity
- **Cons**: Users must check app for notifications; poor for async collaboration
- **Rejected**: Email is core to collaboration workflow
## References
- Postmark API docs: https://postmarkapp.com/developer
- keighl/postmark Go library: https://github.com/keighl/postmark
- Email threading RFC: https://www.ietf.org/rfc/rfc2822.txt

View File

@@ -0,0 +1,69 @@
# ADR-007: Security Model
## Status
Accepted
## Context
The platform handles sensitive documentation and must resist supply-chain attacks, unauthorized access, data tampering, and common web vulnerabilities. The user explicitly requested a "paranoid" approach.
## Decision
Implement defense in depth with the following layers:
### 1. Supply Chain Security
- **Minimal dependencies**: Only well-established, security-audited libraries
- **Pinned versions**: All Go modules pinned with go.sum checksums
- **Vendoring option**: `go mod vendor` for air-gapped builds
- **No build scripts**: Makefile only — no npm install, no curl | bash
- **Reproducible builds**: Same source → same binary hash (via `-trimpath`)
### 2. Authentication Security
- **Passkeys primary**: Phishing-resistant WebAuthn/FIDO2
- **Argon2id fallback**: Password Hashing Competition winner
- **Constant-time comparison**: All secret verification uses `subtle.ConstantTimeCompare`
- **Rate limiting**: Auth endpoints: 5 attempts per minute per IP
- **Session hardening**: Signed, HttpOnly, Secure, SameSite=Strict cookies
### 3. Authorization Security
- **RBAC**: Read/write/admin per document and collection
- **Principle of least privilege**: Default to no access, explicitly grant
- **Content verification**: SHA-256 hash verified on every read
- **Audit logging**: Every access logged with actor, resource, timestamp, IP
### 4. Input Validation
- **Markdown sanitization**: Goldmark renders to safe HTML; no raw HTML injection
- **File upload validation**: Magic number check, extension whitelist, size limits
- **Path canonicalization**: All file paths resolved to absolute, checked against allowlist
- **Query parameterization**: All SQL uses parameterized queries (no string concatenation)
### 5. Transport Security
- **TLS 1.3**: Minimum version enforced
- **HSTS**: Strict-Transport-Security header with preload
- **CSP**: Content-Security-Policy restricts script sources to nonce-tagged inline and same-origin
- **CORS**: Restrictive — only same-origin by default
### 6. Operational Security
- **No secrets in logs**: All tokens, passwords redacted
- **No secrets in environment**: Configuration from file or encrypted env vars
- **Read-only container**: Filesystem mounted read-only except `/data`
- **Non-root user**: Container runs as unprivileged user
- **Health checks**: `/health` endpoint verifies all dependencies
## Consequences
### Positive
- Resistant to OWASP Top 10 vulnerabilities
- Supply-chain attacks mitigated by minimal dependencies
- Audit trail provides accountability
- Container hardening reduces attack surface
### Negative
- CSP can block legitimate resources (requires careful tuning)
- Rate limiting may affect legitimate users behind NAT (mitigated by user-aware limits)
- Security reviews required for any new dependency
- More complex local development (TLS certs, CSP headers)
## References
- OWASP Top 10: https://owasp.org/www-project-top-ten/
- Go Security Best Practices: https://go.dev/security
- WebAuthn Security: https://www.w3.org/TR/webauthn-2/#security-considerations
- Content Security Policy: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP