Bundle loading with no HTTP: walk content/, split YAML frontmatter, derive an NFC-normalised key and a language from the filename, and lift only title out of frontmatter so every other key stays readable through Extra (ADR-0002). Path safety is os.Root rather than a hand-rolled cleaner (ADR-0031). os.DirFS documents that it does not prevent symlink escape; os.Root refuses any name resolving outside the root, so the guard is a property of the type instead of a check to remember at each call site. Test: a symlink to a file above the root cannot be read. This clears the traversal item off the latent list. A bundle that will not parse is logged and skipped, never fatal (ADR-0029), as is a key claimed by two spellings of one variant (ADR-0021). Bundle carries only Key, Lang, Path, Title, Body and Extra; Date, Slug, Draft and Aliases arrive with the features that read them.
4.3 KiB
State
Verified against: 1c19727 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 |
217 |
internal/content/content_test.go |
table-driven; symlink-escape evidence for the path guard | 155 |
No HTTP yet. 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 | 0 | 2 | Resolver extraction |
| Collection pages | 0 | 1 | Query primitive |
| Views / output formats | 0 | 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 | 2 direct, 7 modules | 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 | The coupling gate makes forgetting them impossible, which is the real failure mode; checking values needs code to count | 3rd transform or 2nd route |
| No mechanical gate on the untrusted boundary (ADR-0003) | Nothing untrusted exists yet | The comment path, Arc 3 — a test that untrusted input reaches no shortcode or template evaluation |
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
The prompt sequence to a Grav-level engine. Completed through 1. Update this line as each lands; a context refresh loses the conversation, not the plan.
- 0 · ADRs: pagination shape (0028), parse-failure policy (0029), rename (0030), path guard (0031)
- 1 · bundle loading:
os.Root, frontmatter, key/lang, NFC, collisions, skip-loudly - 2 · serve a bundle at its permalink:
-site, trailing-slash redirect, goldmark, reference theme - 3 · language variants:
/bn/…, fallback chain,/en/…redirect (2nd routing case → resolver) - 4 · aliases
- 5 · section index pages, paginated (first collection page → Query)
- 6 · settings cascade (re-adopt from
ideas/deferred-decisions.md) - 7 · declared content types (re-adopt)
- 8 · template composition: per-type sets, block override, site
templates/ - 9 · tags: global namespace, tag listings (re-adopt)
- 10 · sequences: series, sparse order, prev/next/archive
- 11 · typography + Bengali numerals transforms
- 12 · shortcodes (3rd transform → Stage pipeline)
- 13 · image derivatives (first Effect)
- 14 · feeds: primary, per-section, per-tag (2nd Effect → Effect runner)
- 15 · sitemap, robots, OpenGraph/JSON-LD
- 16 · in-process page cache with the validity record (re-adopt)
- 17 ·
checkcommand - 18 ·
newcommand - 19 ·
-dev: reveal drafts and future-dated, template reload - 20 · extras (re-adopt)
- 21 · polled change detection + Dockerfile
- 22 · complete the reference theme against the full contract
Arc retro log
One line per completed arc: what it cost, what it taught, what it made unnecessary.
- (empty)