# State **Verified against:** `fbf24f7` on 2026-07-30 — update this line every change. If this file disagrees with the code, the code is right and this file is a bug. ## Inventory | File | Purpose | LOC | |---|---|---| | `go.mod` | module `khosra`; `goldmark`, `x/text`, `yaml.v3` direct | 10 | | `internal/content/doc.go` | package comment | 5 | | `internal/content/content.go` | bundles: `os.Root` open, walk, frontmatter split, key/lang derivation, NFC, tag slugs, permalink building | 352 | | `internal/content/site.go` | the indexed site: lookup with language fallback, aliases, `Query` and `Run`, sections, `Sequence` | 286 | | `internal/render/render.go` | goldmark, per-kind template sets with site override, `Page`/`List`/`Sequence`/`head` | 297 | | `internal/render/templates/` | reference theme: `base.html`, `page.html`, `list.html`, `theme.css` (ADR-0026) | — | | `internal/web/resolve.go` | URL → (key, lang, page, tag) or a canonical redirect: language prefix, `/en/…` fork guard, pagination, tags, trailing slash | 112 | | `internal/web/web.go` | handler: resolve, look up with fallback, section and tag listings, sequence, `/static/`, degrade on failure | 156 | | `cmd/khosra/main.go` | flags, wiring, startup — the only place things are assembled | 53 | | `*_test.go` | table-driven; symlink escape, permalink, language fallback, aliases, pagination, tags, sequences, 404 | 1011 | Serves a bundle at `/{section}/{slug}/`, a paginated listing per section, tag listings global and section-narrowed, sequence navigation and a series archive on any nested bundle, and `static/` verbatim. This repo holds engine source only — the site root is external and passed with `-site` (ADR-0011). Dependencies: three, all allowlisted — `goldmark`, `golang.org/x/text`, `gopkg.in/yaml.v3`. ## Counters — the earn-it authority Never anticipate a threshold. Increment when the code lands, then check whether the extraction is *due this change*. | Counter | Now | Extraction due at | What it buys | |---|---|---|---| | Render transforms | 0 | **3** | Stage pipeline (ordered `func(ctx,*Page) error`) | | Routing cases | 5 | **2** — done | Resolver at `internal/web/resolve.go`: bundle, language prefix, pagination, tag, section-narrowed tag | | Collection pages | 4 | **1** — done | Query primitive: `content.Query{Section, Tag, Lang}` + `Site.Run`. The fourth — a series archive — resolves through `Site.Sequence` instead: membership is structural and the sort ascends, so it shares the index but not the Query | | Views / output formats | 2 | **2** — due | Two template sets exist (bundle, listing); the View layer is Arc 2's third item | | Effects | 0 | **2** | Effect runner + trigger wiring (change / schedule / demand) | | Extensions | 0 | **3** | Extension registry + wire file (`extensions.md`) | | Interface implementations | — | **2** | The interface itself | | Non-stdlib dependencies | 3 direct | budget in `scripts/budgets.env` | — | Allowlist, all three imported: `goldmark` (markdown), `golang.org/x/text` (NFC, ADR-0015), `gopkg.in/yaml.v3` (frontmatter, ADR-0020). ## Latent items — known, deliberately unfixed Do not fix these mid-feature. They become features when the human says so. An arc does not close with an untriaged item: at each arc boundary every row is fixed, scheduled into an arc, or accepted with a stated reason. A list nothing drains is a graveyard of known defects. | Item | Why it waits | Trigger to fix | |---|---|---| | No mechanical check that the counters are *correct* | Accepted at the Arc 1 boundary: the coupling gate makes forgetting them impossible, which is the real failure mode, and checking the values needs code to count | 3rd transform (queue 12) | | No mechanical gate on the untrusted boundary (ADR-0003) | Scheduled to Arc 3: nothing untrusted is read yet | The comment path — a test that untrusted input reaches no shortcode or template evaluation | | `date` stays in `Extra` after being lifted onto `Bundle.Date`, unlike `title`, `aliases`, `tags` and `order`, which are deleted | Spotted while adding `order`; the theme contract says `Extra` holds what the parser does not name, so one of the two is wrong. Harmless today — a template reading `.Extra.date` gets the raw YAML value | Whatever next reads `Extra` generically: feeds (queue 14) or `check` (17) | | Sequence resolution rescans the index on every bundle request — two passes over every key, each doing a `Lookup` | No cache exists anywhere yet, and a site of this size resolves in microseconds. Measuring first is the rule (queue 16) | The page cache (queue 16), which is the thing that makes the cost visible | | Raw HTML in Markdown is currently omitted only because goldmark's default omits it | Verified at the Arc 1 boundary, and it is what keeps invariant 2 intact for authored content. `html.WithUnsafe()` is the obvious move when a shortcode needs to emit HTML, and it silently turns authored Markdown into an injection path | Shortcodes (queue 12) — if unsafe rendering is enabled, the trusted/untrusted split must be real code, not a default | ## Open questions None. Nothing blocks Arc 1 or the first deploy. Every ADR in `decisions.md` is accepted; none is open or proposed. ## Build queue Working plan lives in `.scratch/build-queue.md`, which is deliberately not committed — `git log` is the record of what actually landed. If that file is absent, read the log and rebuild the plan from it. ## Arc retro log One line per completed arc: what it cost, what it taught, what it made unnecessary. - **Arc 1 — the spine.** 619 core lines, 3 dependencies, 4 queue entries. Taught: `os.Root` makes the path guard a property of the type, so the latent item that shipped with the harness died instead of being implemented; and running the gates against real code found six defects *in the gates* — an allowlist parser that rejected its own documented format, two advisories that fired only on correct code, a coupling gate that demanded explanations for permission edits, an untidy `go.mod` hiding a direct dependency, and a nesting check off by one level. Made unnecessary: a hand-rolled traversal cleaner, and a second routing branch — the resolver arrived by counter at exactly the right moment.