Files
khosra/docs/state.md
T
bdeshi 5e4909410b serve the files a bundle owns
Every figure and gallery shipped so far emitted links a browser could not fetch: a
relative src resolves under the page's URL, and nothing answered there. Found by
fetching the pages' own links rather than by reading their markup — the evidence
runs had been checking that the right src appeared, never that it worked.

A directory bundle's files are now served under its URL. The bundle is looked up
first and the file is read only from the directory that bundle owns, never from a
path assembled out of the request: ADR-0024 requires that no route serve bundle
bytes by path alone, since every byte inside a bundle inherits its publish status.
When drafts arrive at queue 19 the filter belongs beside that lookup and nowhere
else, which is why the ordering is written down in the comment.

A single-file bundle owns nothing: its neighbours belong to the section, and its
slash-terminated URL has nothing beneath it. An author with assets writes a
directory bundle, now stated in content-model.md.

A .md inside a bundle directory is never an asset — it is a bundle with its own URL
or a fragment that was never addressable, and serving either raw would publish
source. http.ServeFileFS handles content type, conditional requests and ranges,
none of which is worth reimplementing here.
2026-07-31 02:40:03 +06:00

9.0 KiB

State

Verified against: ebf63d1 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, partial files, permalink building 381
internal/content/settings.go site.yaml: the site's own declarations (base, title) and absolute-URL building (ADR-0039) 59
internal/content/site.go the indexed site: lookup with language fallback, aliases, Query and Run, sections, Sequence, Everything, slug routes 395
internal/render/render.go goldmark with the typographer, per-kind template sets with site override, the Partial/Origin seams features render and resolve through, Page/List/Sequence/head 409
internal/render/chrome.go the engine's own words: phrase table, month names, digits, and the t/num/day template funcs (ADR-0034) 105
internal/render/templates/ reference theme: base.html, page.html, list.html, shortcodes.html, theme.css (ADR-0026)
internal/ext/shortcodes/ first feature: {{< name key="value" >}} block parser and node renderer, rendering through a theme fragment (ADR-0036). figure, gallery, include 315
internal/ext/widows/ second feature: joins the last two words of a paragraph or heading with a non-breaking space, over the tree so code spans are safe 108
cmd/khosra/wire.go the only list of enabled features (extensions.md) 20
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/asset.go files inside a bundle's own directory, looked up through the owning bundle so visibility can only ever inherit (ADR-0024) 58
internal/web/discover.go /robots.txt and /sitemap.xml, absolute and only with a declared base (ADR-0039) 74
internal/web/web.go handler: resolve, look up with fallback, section and tag listings, sequence, /static/ (misses and refusals alike answer 404), degrade on failure 152
cmd/khosra/main.go flags (-site, -addr, -base), wiring, startup — the only place things are assembled 60
*_test.go table-driven, one file per source file; symlink escape (content and static), canonical paths, language fallback, aliases, pagination, tags, sequences, chrome, typography, shortcode escaping, galleries, includes, partials, widows, site settings, absolute URLs, robots, sitemap, slug routes, bundle assets, 404 2050

Serves a bundle at /{section}/{slug}/ — the slug derived, or declared in frontmatter without moving the key (ADR-0035) — a paginated listing per section, tag listings global and section-narrowed, sequence navigation and a series archive on any nested bundle, static/ verbatim, a directory bundle's own files under its URL, plus /robots.txt and /sitemap.xml. Chrome text, dates and digits render in English or Bengali; authored text is untouched but for typographic smoothing and widow prevention (ADR-0034). This repo holds engine source only — the site root is external and passed with -site (ADR-0011). site.yaml declares base and title; with a base, canonical, hreflang and OpenGraph URLs go absolute (ADR-0039).

Frontmatter the parser lifts today: title, date, tags, aliases, order, slug. Every other key in content-model.md's table — including draft and type — lands in Extra unread, so that table is the accepted format, not a list of what runs.

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 — page-level only 0 3 Stage pipeline (ordered func(ctx,*Page) error). Parse-phase work does not count and must not: goldmark's extender list is already an ordered pipeline for it, so typography, shortcodes and widows compose there (cmd/khosra/wire.go) and a second pipeline beside it would be pure duplication. This counts transforms over the assembled page, which nothing hosts yet — OpenGraph and JSON-LD (queue 15) are the first candidates
Routing cases 8 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 2 3 Extension registry (extensions.md). The wire file arrived with the first feature rather than the registry — cmd/khosra/wire.go, one line, no struct
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 The first page-level transform (queue 15), now that the transform counter means something narrower
No mechanical gate on the untrusted boundary (ADR-0003) Scheduled to Arc 3: nothing untrusted is read yet. Half of it is now mechanical — verify.sh rejects WithUnsafe, so authored Markdown cannot become HTML — but there is still no check that a future untrusted source stays out of shortcode and template evaluation 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)
The reference theme's images carry no width/height, and a gallery's carry no alt — below the output floor conventions.md states Nothing can supply them yet: dimensions need the image read, and a filename is not alt text. An empty alt is at least honest about a picture nothing describes Image derivatives (queue 13) compute dimensions; structured gallery items with captions land with them (ADR-0037's revisit note)
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

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.