# `gno.land/r/moul/blog` moul's personal blog, on chain. One author, posts stored as data rather than code, and a URL that is meant to survive being pasted into someone else's chat. ``` gno.land/r/moul/blog the index, newest first gno.land/r/moul/blog: one post gno.land/r/moul/blog:t/ every post carrying a tag ``` ## Why private, and why no `/vN` These are the same decision seen from two sides. A post URL leaves this repository the moment it is published: it goes into chats, into other people's articles, into a link someone bookmarked. None of those can be rewritten. A `/v1` would break all of them at once, which is the one failure a blog cannot absorb, so the module line carries no version. What normally pays for a version is the ability to change the code. Here that comes from `private = true` instead: a private package may be re-added at the same path by the address in `[addpkg].creator` (`gno.land/pkg/sdk/vm/keeper.go`, `checkRedeployPermission`), and nothing else may import it. So the code can be replaced without the path moving. The bill for that arrives as a wipe. A redeploy re-runs `init()` and resets every package-level variable, so **every post is erased**. That is survivable only because the markdown is kept off chain and `tools/gnoblog -all` pushes all of it back in one transaction. The chain holds the published copy; it is not the only copy. `tools/gnocontracts` records the exemption from the repository's "every path ends in `/vN`" rule, next to `r/moul/home`'s, with the reason. ## The API Writes are restricted to `g1manfred47kzduec920z88wfr64ylksmdcedlf5`. | function | what it does | | --- | --- | | `Set(cur, slug, title, date, tags, body)` | create or replace a post, the ordinary publish | | `Append(cur, slug, body)` | append, for a body too large for one transaction | | `Delete(cur, slug)` | remove a post | | `SetIntro(cur, body)` | the markdown above the index; `""` restores the default | | `Get(slug)` | one post's raw markdown | | `Count()` · `Revision()` | how many posts, and how many writes have been accepted | | `Manifest()` | `slug⇥rev⇥date⇥len⇥sha256` per post, the diff surface | A slug is 1 to 64 bytes of `[a-z0-9._-]`. No `':'` and no `'/'`, so a slug can never break out of its own render path. `intro` is refused, because `intro.md` names the index header on the local side and a post there would have nowhere to live. A date is exactly `YYYY-MM-DD`, and that is load-bearing rather than cosmetic: posts are kept in a second avl tree keyed by `"/"` and walked in reverse, so lexical order has to be chronological order. gno has no `sort.Slice`; this is what replaces it. ## `Manifest()` is the whole sync protocol One `vm/qeval` returns a line per post carrying a sha256 over the title, date, tags and body **together**. So `gnoblog status` can say exactly which local file needs a transaction without downloading a single body, and changing any one of the four fields changes the hash. Both halves build that hashed record the same way, and neither can check the other at run time: the realm's test asserts the record, and the tool's test pins its digest as a literal. If one side ever drifts, the tool reports every post as up to date while the chain holds the old text. ## Escaping The body renders **verbatim**. It is markdown written by the one address that can call `Set`, and escaping it would turn every heading and link in a post into literal text. Titles, tags and index excerpts go through `p/moul/kit/ui` anyway. Not because the author is untrusted, but because a title with a `]` in it would otherwise break the link it sits inside, and that is a rendering bug with no attacker in it at all. ## Publishing a post The markdown lives outside this repository; `tools/gnoblog` never defaults to a path to it. ```sh go -C tools tool gnoblog -content DIR status # what differs from the chain go -C tools tool gnoblog -content DIR tx # the transaction that fixes it ``` `tx` writes an unsigned transaction document and the two `gnokey` commands that sign and broadcast it. Several posts go in one document, so publishing three is one signature and one atomic broadcast. After a redeploy, `tx -all`. --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:** ![gno.land/r/moul/blog dependency graph](https://raw.githubusercontent.com/moul/gno-contracts/main/_assets/gno.land/r/moul/blog/deps.png) > ⚠️ **Disclaimer:** provided as-is, without warranty; not security-audited. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md).