Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Issues and requests are frontmatter, not JSON

307cbc2aa624ca9b76b79849e8ce8926414be435 · Keenan Nemetz · 17d ago · parent 427c4761

Issues and requests are frontmatter, not JSON

issues/<ulid>/issue.md and requests/<ulid>/request.md replace meta.json plus
description.md, and approvals/<account>.md replaces approvals/<account>.json.
Every file on refs/heads/vole is now frontmatter markdown read by one package, so
there is one reader and one writer for issues, requests, comments and approvals
instead of a JSON path and a hand-rolled key/value path.

The diff is the interface. A JSON array rewrites its neighbour to add a comma, so
one label cost two changed lines and removing one cost three; a block sequence
costs one. docs/spikes/issue-frontmatter measured the same six edits at 25 changed
lines as meta.json and 10 as frontmatter, and one file per entity means one path
to diff, blame and log.

Three rules make YAML safe to hand-edit rather than something to police:

- the writer always double-quotes free text, so no indicator, reserved word or
  " # " can change what a title means
- a label is quoted only when the parser would not read it back bare ("null"),
  which leaves ordinary labels as they were
- a reader refuses an unquoted title that the parser read differently: "title:
  fix #12 crash" would otherwise be stored as "fix" and nobody would find out

Control characters are refused on both sides, so a tree the server accepts is a
tree it can always rewrite.

`updated` is gone from issues, requests and the index's issues table. It cost a
changed line on every edit — 11 of the 21 frontmatter lines in the spike's
sequence — and the commit that changed the file already carries the time.

Verified: TestRenderParseRoundTrip puts 13 titles that break naive YAML (hash,
colon, leading dash, null, quotes, backslash, unicode, 190 bytes) through the
writer and the reader; the server tests push a rendered issue over SSH and render
it, and the approval-ownership tests withdraw approvals/<account>.md. Then
scripts/ci.sh green (gofmt, vet, build, markdown links, go test -race ./...).

README.md +22−12

@@ -101,23 +101,33 @@ over HTTP — not a `403` — so the read-only surface never confirms that it ex
101 101
102 102 ## Filing an issue
103 103
104 An issue is a directory on the `vole` branch. No client tooling is required:
104 An issue is one file on the `vole` branch. No client tooling is required:
105 105
106 106 ```sh
107 107 git switch --orphan vole
108 108 ID=01M5JR2JK29KKMAA1T9MDNTPBP # any 26-char ULID; unique per issue
109 109 mkdir -p issues/$ID
110 cat > issues/$ID/meta.json <<'JSON'
111 {"title": "Nothing renders on the docs page",
112 "state": "open",
113 "author": "you",
114 "created": "2026-10-22T21:30:00Z",
115 "labels": ["bug"]}
116 JSON
117 printf 'Steps to reproduce...\n' > issues/$ID/description.md
110 cat > issues/$ID/issue.md <<'MD'
111 ---
112 title: "Nothing renders on the docs page"
113 state: open
114 author: you
115 created: 2026-10-22T21:30:00Z
116 labels:
117 - bug
118 ---
119 Steps to reproduce...
120 MD
118 121 git add -A && git commit -m "issue: docs page blank" && git push origin vole
119 122 ```
120 123
124 The head is frontmatter, the rest is the body. That shape is the reason the
125 format exists: closing an issue changes one line, adding a label adds one line,
126 and `git blame` still attributes the title to whoever wrote it. Free text
127 (`title`) is always double-quoted, so a `#` or a leading `-` cannot change what
128 it means; a title typed without quotes that YAML would read differently is
129 refused by name rather than stored as something shorter.
130
121 131 That branch holds Vole's metadata and is deliberately narrow:
122 132
123 133 - Only `issues/` and `requests/` may appear in it; anything else is rejected at
@@ -195,9 +205,9 @@ Admins can push a protected branch directly. Every merge is recorded in
195 205 An approval can only be written by the person it names: the hook compares the path
196 206 to the SSH identity the server resolved from your key, so `approvals/keenan.json`
197 207 cannot be created *or edited* by anyone else, and only keenan can withdraw it (an
198 admin can, and that is audited). The names in `meta.json` — `author` on an issue or
199 request — are display fields and carry no authority; the approval path is the only
200 name that decides who may land a commit.
208 admin can, and that is audited). The names in the frontmatter — `author` on an
209 issue or request — are display fields and carry no authority; the approval path is
210 the only name that decides who may land a commit.
201 211
202 212 ## CI
203 213