/invariants at the arc boundary: 1, 3 and 5 held and were checked rather than assumed; 2, 4, 6, 7, 8 and 9 are not yet applicable, with the pre-freeze core figure recorded as 619. Invariant 2 turned up a load-bearing default nobody had written down: goldmark omits raw HTML unless WithUnsafe is set, verified, and that is the only reason authored Markdown cannot inject script today. The obvious move when a shortcode needs to emit HTML removes it, so it is now a latent item triggered by queue entry 12 rather than a surprise. The other two latent items are triaged as the arc-close rule requires: the counter-correctness gap is accepted with a reason, the untrusted-boundary gate is scheduled to Arc 3.
4.7 KiB
State
Verified against: 8a6857b 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; x/text, yaml.v3 direct |
8 |
internal/content/content.go |
site root → bundles: os.Root open, walk, frontmatter split, key/lang derivation, NFC, collision drop, key index, language fallback, alias index, URL building |
358 |
internal/render/render.go |
goldmark + the embedded reference theme; Page is what templates receive |
92 |
internal/render/templates/ |
reference theme: base.html, theme.css (ADR-0026) |
— |
internal/web/resolve.go |
URL → (key, lang) or a canonical redirect: language prefix, /en/… fork guard, trailing slash |
56 |
internal/web/web.go |
handler: resolve, look up with fallback, render, degrade on failure | 62 |
cmd/khosra/main.go |
flags, wiring, startup — the only place things are assembled | 50 |
*_test.go |
table-driven; symlink escape, permalink, redirect, 404 | 245 |
Serves a bundle at /{section}/{slug}/. This repo holds engine source only — the site root is external
and passed with -site (ADR-0011).
Dependencies: none.
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 | 2 | 2 — done | Resolver extracted at internal/web/resolve.go |
| Collection pages | 0 | 1 | Query primitive |
| Views / output formats | 1 | 2 | View layer (contract per theme-contract.md) |
| 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 |
— |
Allowlisted, in use: goldmark is not yet imported. Allowlist: 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 |
| 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 blocking Arc 1
None. Every decision the engine needs before Arc 1 and before the first deploy is recorded.
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.Rootmakes 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 untidygo.modhiding 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.