#1 m1/r1-spike

mergedpassopened
details
branches
master ← m1/r1-spike
updated
id
99bb4934-7f03-4ec5-9688-23e6b1faa883

Patch

docs/deps-decision.md+109 −1
diff --git a/docs/deps-decision.md b/docs/deps-decision.mdnew file mode 100644index 0000000..1bf93b3--- /dev/null+++ b/docs/deps-decision.md@@ -0,0 +1,109 @@1+# hu13 deps decision (M1/R1 spike, f0rge issue #1, read-only)2+3+Spiked 2026-09-10 in scratch cargo project under `/tmp` (not in repo)4+against real f0rge data at `~/src/code/gl0w-w0rld/f0rge/.forge`.5+Total direct deps: **4** (limit ≤ 5). All pins exact (`=`), f0rge-style.6+7+## Pinned set8+9+```toml10+[dependencies]11+pulldown-cmark = "=0.13.4"12+serde = { version = "=1.0.229", features = ["derive"] }13+serde_json = "=1.0.151"14+yaml_serde = "=0.10.7"15+# dates: hand-rolled, zero deps (copy f0rge-core is_rfc3339_utc shape)16+# serve: std only (TcpListener + thread-per-connection), no tokio/hyper17+```18+19+`serde`/`serde_json` pins match f0rge workspace pins on purpose20+(`f0rge/Cargo.toml`: `serde =1.0.229`, `serde_json =1.0.151`).21+`yaml_serde =0.10.7` is f0rge's YAML pin (the maintained22+`serde_yaml` rename); spike ran on `serde_yaml =0.9.34` then23+verified `yaml_serde =0.10.7` drop-in (see §2).24+25+## 1. Markdown → HTML: pulldown-cmark =0.13.426+27+Why: CommonMark-faithful, zero-JS output, no network/build deps,28+`Options::empty()` is enough for f0rge bodies + wiki; already proven29+at this version via `cargo fetch` + `cargo run`.30+31+Spike: rendered real `issues/<uuid>/issue.md` body (164 B md →32+171 B html), real `prs/<uuid>/pr.md` body (202 B → 210 B), and full33+`wiki/docs/PLAN.md` (21598 B → 25377 B). All produced `<p>`/`<h1>`.34+Code spans escape correctly (`` `<script>` `` → `<code>&lt;script&gt;</code>`).35+36+XSS finding (honest): **raw `<script>` is NOT escaped by default.**37+Probe `# hi / <script>alert(1)</script> / <img src=x onerror=…>` →38+`"<h1>hi</h1>\n<script>alert(1)</script>\n<img …>\n"`39+(raw present, escaped absent). This is correct CommonMark40+passthrough, but wrong for hu13's untrusted inbox-adjacent bodies.41+Fix (spiked, zero extra dep): map42+`Event::Html(h)/InlineHtml(h) → Event::Html(escape_html(&h))`43+with a 5-line hand-rolled `escape_html` (`&<>"'`), since mapping to44+`Text` double-escapes. Verified safe output:45+`"&lt;script&gt;alert(1)&lt;/script&gt;"`, raw absent, legit46+`**bold**`/lists intact. Diffs/logs never go through markdown —47+always `<pre>` + same `escape_html`.48+49+Fallback: `pulldown-cmark =0.12.1` (same `Parser/html::push_html`50+API for our use). Never comrak (bigger deps, server-side novelty51+we don't need).52+53+## 2. Frontmatter: split `---` + yaml_serde =0.10.754+55+Why: f0rge schema is `---` fences + YAML body; `yaml_serde` is the56+maintained `serde_yaml` (0.9.x is `+deprecated`), same API, and the57+exact crate f0rge-core pins, so hu13 parses byte-identically.58+59+Spike (`serde_yaml =0.9.34`, `serde =1.0.229`, `serde_json =1.0.151`):60+25/25 real files parsed — 12× `issues/*/issue.md` + 5× `prs/*/pr.md`61++ 6× `tasks/*.md` + `labels.md` (4 entries, YAML list, no fences) +62+`milestones.md` (2 entries). Typed structs with `#[serde(default)]`63+for optional `labels/assignees/milestone/updated`.64+Equivalence probe on `yaml_serde =0.10.7`: unknown field tolerated,65+`missing field \`id\`` error identical — drop-in confirmed.66+67+- Unknown fields tolerated: `zzz_future: 1` → `Ok` (true).68+- Missing required field names file+field: hu13 formats as69+  `{basename}: {serde msg}` → e.g. `missing-title.md: missing field \`title\``70+  (`msg.contains("title")` true), matching f0rge `MissingField{file,field}`.71+- Zero panics: 10 hostile inputs via `catch_unwind`, 0 panics —72+  `spec/fixtures/invalid/{bad-ts,bad-uuid,missing-title}.md`,73+  `spec/fixtures/sec/alias-bomb.md`, golden `issue.md` (control ok),74+  plus `""`, `---/---`, unclosed fence (→ `nofront`), NUL bytes75+  (→ clean `Err`, no panic), 200 KB single line (→ `Ok`, cap handles76+  before parse per schema SEC-01 256 KiB).77+78+Fallback: `serde_yaml =0.9.34` (identical API, deprecated warning79+only). Keep f0rge's pre-parse guards (size cap, indent/flow depth,80+alias count) in hu13 build — YAML lib alone is not the guard.81+82+## 3. Dates: hand-rolled RFC3339-UTC (no chrono/jiff) + serde_json =1.0.15183+84+Why hand-rolled: schema needs only validation + `(created,id)` sort,85+and lexicographic == chronological for fixed-width UTC output.86+f0rge-core already proves it (`is_rfc3339_utc`, `unix_to_rfc3339`,87+`now_rfc3339`, `forbid(unsafe_code)`); adding chrono/jiff buys88+arithmetic/timezones hu13 never uses, costs a 5th dep + binary size.89+Spike validator (f0rge shape: `YYYY-MM-DDTHH:MM:SS[.1-9frac]Z`):90+23/23 real `created` fields ok, 0 bad; `08/09/2025 02:00` correctly91+rejected. `serde_json` roundtrip (`json!` → `to_string` →92+`from_str`) ok.93+94+Fallback: `chrono =0.4.x` with `features=["serde"]` + `DateTime<Utc>`95+if arithmetic ever needed; prefer jiff over chrono only if a date96+dep becomes mandatory (smaller, safer API). Not needed for v1.97+98+## 4. Serve: std TcpListener + thread-per-connection, no tokio/hyper99+100+No code in this spike, decision only: the only dynamic surface is101+2 write-only text POST routes (issue/PR inbox → `/data/inbox`,102+never read back over HTTP, never rendered). Traffic is human +103+occasional agent curl — single-digit req/s. A std `TcpListener`104+accept loop + `thread::spawn` per connection + ~50-line HTTP/1.1105+parser (`Content-Length` only, urlencoded form, 256 KiB cap, no106+keep-alive tricks) is enough; static site is prebuilt files.107+tokio/hyper add async runtime + dep tree for zero benefit here.108+Fallback: `tiny_http` single-file crate if std parser ever feels109+hand-rolled-critical — still no async.0-- 2.55.0

Comments (0)

No comments yet.

Reviews (0)

No reviews yet.

all pulls