Commit Graph
13 Commits
Author SHA1 Message Date
Claude Opus 5andbdeshi 01ac1bf431 close Arc 1: invariants checked, latent list triaged, retro written
/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.
2026-07-30 02:01:48 +06:00
Claude Opus 5andbdeshi 159e5feb94 harness: a standing advisory is a defect
The %w advisory counted every fmt.Errorf without %w, including calls that create
an error rather than wrap one — so it fired permanently on correct code. It now
looks for a call that passes an err and drops the %w, which is the actual rule.

Recorded the general form in the loop's Verify step and in HARNESS.md: a warning
that fires on correct code and keeps firing is a defect in the check or the code,
resolved in that change. Two advisories have now been narrowed after firing on
code the harness itself mandates, and the count creeping from one to two was the
only signal either time.

verify.sh is at zero warnings, which is what makes the next one legible.
2026-07-30 02:00:09 +06:00
Claude Opus 5andbdeshi c3fc89a913 honour aliases as permanent redirects
An alias is a promise that an old URL keeps working, so it answers 301 to the
canonical one rather than serving the same content twice (ADR-0008). Frontmatter
takes a scalar or a list and tolerates surrounding slashes, because authors write
both.

Ambiguity is dropped, not resolved: an alias naming a real bundle, or claimed by
two bundles, is logged and ignored so the real bundle keeps its URL. Aliases
compose with language prefixes for free, since the resolver splits the language
before the key is looked up.

The redirect still fires only for an alias that exists, so a nonexistent path
cannot be probed by 301 — the property prompt 3 established.

Evidence: /pages/bio/ and /about/ both 301 to /pages/about/, /bn/pages/bio/ 301s
to /bn/pages/about/, and /pages/nothing/ is 404.
2026-07-30 01:45:59 +06:00
Claude Opus 5andbdeshi 275f67dc52 serve language variants under a prefix
The default locale stays at the root; every other language is the same key under
/{lang}/ (ADR-0009). /en/… is never live and redirects to the root form so the URL
space cannot fork. Lookup now takes a language and reports which one it served,
following requested → default → any rather than 404ing when a translation is
missing.

That is the second routing case, so the resolver is extracted to resolve.go and
the mux keeps one entry: URL shape is the resolver's business. A leading segment
counts as a language only when some bundle is written in it, so an unknown prefix
is a 404 rather than a stripped path — and a section may not be named after a
language in use, now recorded in content-model.md.

Because the served variant can differ from the URL requested, Page gained
.Canonical (the variant actually served) and .Alternates for hreflang. A theme
must never build a path, so both come from the engine.

Evidence: /bn/pages/about/ serves the Bengali body with lang="bn" and canonical
/bn/pages/about/; /bn/posts/hello-world/ falls back to English with canonical
/posts/hello-world/; /en/pages/about/ 301s to /pages/about/; /fr/… is 404.
2026-07-30 01:43:12 +06:00
Claude Opus 5andbdeshi d0197a41ed add a Makefile and build instructions
So the thing can be built and run without reading the harness. Eight targets,
each one go command or verify.sh; make help lists them. README gains the four
lines someone actually needs, and says a site root is a directory the binary is
pointed at rather than something in this repo.

make is recorded in toolchain.md as a convenience: the gate never invokes it, so
a machine without make loses nothing but typing.
2026-07-30 01:38:17 +06:00
Claude Opus 5andbdeshi 519a41cf2f serve a bundle at its permalink
-site (or KHOSRA_SITE) opens the site root through content.OpenSite, so every
read keeps the os.Root guarantee. A path is a bundle key: /{section}/{slug}/
serves, the slashless form redirects permanently to it (ADR-0008), anything
unknown is 404. Render failure logs and returns a bare 500 rather than leaking a
template or filesystem detail.

internal/render holds goldmark plus the embedded reference theme (ADR-0026):
base.html with a redefinable "main" block, and one stylesheet inlined through
.Style. Serving it at an asset route would have been a second routing case for no
gain, and static serving belongs to a later entry.

Evidence beyond the tests: the binary against a real site root returns 200 with
<h1>About</h1> and the rendered body, 301 from /pages/about to /pages/about/, and
404 for /nope/. A Bengali variant is scanned but not yet reachable — that is the
next entry.

theme-contract.md gains a "Live today" section listing the six fields and two
named templates a theme may now rely on; the rest stays marked as shape.
2026-07-30 01:35:50 +06:00
Claude Opus 5andbdeshi 242bd60d9c keep the build queue in an uncommitted .scratch/
The queue is a working plan, not a record: git log already carries what landed on
each step, so committing the plan duplicates it and invites the two to disagree.
.scratch/ is gitignored and skipped by the gate the way ideas/ and reference/ are,
so exploratory files there need not compile.

state.md keeps a conditional pointer rather than a path it cannot promise: if the
scratch file is absent, the log is the plan.
2026-07-30 01:30:48 +06:00
Claude Opus 5andbdeshi 192b0fb993 content-model: state the language-suffix shape and the collision rule
The parser treats two or three lowercase letters before .md as a language and
rejects a key claimed by two spellings of one variant. Both were implemented and
neither was in the doc that owns the disk contract.

Folded from a separate state commit:
state: point verified-against at the commit that introduced code
2026-07-30 01:30:46 +06:00
Claude Opus 5andbdeshi f1d31437be content: read a site root into bundles
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.
2026-07-30 01:30:12 +06:00
Claude Opus 5andbdeshi 9d817dcadf rename the project to khosra, initialise the module
Naming is free before a module is published, a URL is shared, or a binary is
deployed; every day it waits costs more. Swept every form: module path, binary,
cmd/ directory, KHOSRA_SITE, the feature-loop skill directory, and the prose in
earlier ADRs — which describe this project under its old name, not a different
project. Recorded as ADR-0030.

go mod init lands here rather than with the first feature because the module
path is what the rename is about. x/text and yaml.v3 are required but not yet
imported, so both are indirect and no direct dependency is claimed yet.
2026-07-30 01:14:01 +06:00
Claude Opus 5andbdeshi 126fdbc861 harness: soften two gates that would have taught --no-verify
The test-coupling gate failed comment-only and gofmt-only .go diffs, which
ship no behaviour and owe no test. Bypassing it would have cost every gate at
once, so it now compares added and removed lines with comments, blanks and
whitespace runs stripped: equal sets mean nothing happened. A new .go file is
never exempt.

The exported-doc-comment check is now a warning. No gate can tell
"// Load loads." from a useful sentence, so as a hard failure its cheapest
satisfaction was exactly the noise conventions.md calls worse than nothing.
Package comments and ADR citations in code stay hard.

Also fills in state.md's verified-against line and drops a stray blank line
left in roadmap.md by an earlier gate test.
2026-07-30 01:09:22 +06:00
Claude Opus 5andbdeshi 99f8c730b0 harness: withdraw five pre-code ADRs to deferred ideas
Twenty-seven ADRs existed before a line of Go. Five specified the shape of
unbuilt mechanisms — cache validity, declared types, the settings cascade,
taxonomies, extras — which breaks the rule against abstraction before a
second concrete use, in prose where the counters cannot see it. They move to
ideas/deferred-decisions.md as recorded intent and return shaped by whatever
implements them.

Citations retargeted throughout; where one was decoration the rule now stands
on its own reasoning. Type declarations and the cascade drop to [spec] with
the MVP behaviour stated instead, so the first prompts have less to build.
conventions.md names http.ServeMux as the router, closing a hole that invited
hand-rolling a path splitter. The ADR gate now checks a number is registered
in the log rather than headed by an entry, so withdrawals resolve and invented
numbers still fail. Two architecture invariants corrected: identity no longer
implies a required language suffix, and the duplicated permalink clause is gone.
2026-07-30 01:01:38 +06:00
bdeshiandClaude Opus 5 02268f9121 init
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 00:34:18 +06:00