Move /sama/<discipline> → /sama/discipline/<slug> — hypothesis test of cost-flattening
Second instance of the b32_<old>_url_redirect pattern. Same shape as PR #42's git-url-drop-owner: pure Layer-1 transform + Layer-3 Response wrapper + sed pass on content refs + Bun route change + sitemap handler update. - src/b32_sama_discipline_url_redirect.ts (13 lines, fixed enum of the four spec-frozen discipline slugs) - src/b32_sama_discipline_url_redirect.test.ts (12 cases) - src/d21_handlers_fallback.ts: 301 redirect block placed adjacent to the existing rewriteOldGitUrl block (same pattern, twice now) - src/d21_app.ts: route /sama/:slug → /sama/discipline/:slug; sitemap handler ALL_SAMA mapping → /sama/discipline/<slug> - src/b32_edit_resolve.ts: pageUrl for sama disciplines now emits the new shape; /sama/skill and /sama/v2 unaffected - Sed pass: 13 files in content/ + src/ with /sama/<discipline> URL refs rewritten to /sama/discipline/<discipline> Tests: 419/419 pass (+12 new redirect-helper cases). Co-Authored-By: Claude Opus 4.7 <[email protected]>
17 files changed · +116 −23
content/blog/agentic-coding-corpus-three-patterns.md
+1
−1
| @@ -106,7 +106,7 @@ The prescribed solution: | ||
| 106 | 106 | |
| 107 | 107 | > *"Lock them in a box (can ONLY modify explicitly assigned files), Make them work in phases (CONTRACT → STUB → TEST → IMPLEMENT) with validation gates they cannot skip."* |
| 108 | 108 | |
| 109 | -Read that solution against [SAMA's atomic property](/sama/atomic) (one responsibility per module, ~700-line split) and the [iron law](/sama/skill) (failing test first, verify before next phase). DUMBAI's CONTRACT → STUB → TEST → IMPLEMENT is the iron-law cycle expressed in agent-task vocabulary; "lock them to assigned files" is *Atomic* + *Sorted* enforced as a sandbox boundary. | |
| 109 | +Read that solution against [SAMA's atomic property](/sama/discipline/atomic) (one responsibility per module, ~700-line split) and the [iron law](/sama/skill) (failing test first, verify before next phase). DUMBAI's CONTRACT → STUB → TEST → IMPLEMENT is the iron-law cycle expressed in agent-task vocabulary; "lock them to assigned files" is *Atomic* + *Sorted* enforced as a sandbox boundary. | |
| 110 | 110 | |
| 111 | 111 | **The top comment on `1rug14a`** lands at the same answer from a different angle: |
| 112 | 112 | |
content/blog/sama-empirical-modeled-green.md
+2
−2
| @@ -137,7 +137,7 @@ The blog post is the receipt; the URL is the proof. | ||
| 137 | 137 | |
| 138 | 138 | - Live dogfood: <https://tdd.md/sama/verify?repo=syntaxai/tdd.md> |
| 139 | 139 | - The four checks documented: |
| 140 | - [Sorted](/sama/sorted) · [Architecture](/sama/architecture) · | |
| 141 | - [Modeled](/sama/modeled) · [Atomic](/sama/atomic) | |
| 140 | + [Sorted](/sama/discipline/sorted) · [Architecture](/sama/discipline/architecture) · | |
| 141 | + [Modeled](/sama/discipline/modeled) · [Atomic](/sama/discipline/atomic) | |
| 142 | 142 | - Previous post in this series: |
| 143 | 143 | [When the verifier said "split this"](/blog/sama-empirical-c21-split) |
content/blog/sama-v2-on-ramp-gap.md
+1
−1
| @@ -25,7 +25,7 @@ Specifically: | ||
| 25 | 25 | |
| 26 | 26 | - **The `/goal` workflow rule** lives at `/var/home/scri/.claude/projects/-var-home-scri-Documents-tdd-md/memory/feedback_goal_authoring_workflow.md` — an agent-private memory file. A human contributor literally cannot read it. The rule that determines how every PR on this site is authored is preserved for the agent and invisible to everyone else. |
| 27 | 27 | |
| 28 | -- **The layer-prefix convention** (`a31_*` = Layer 0, `b32_*` = Layer 1, `c14_*` = Layer 2, `d21_*` = Layer 3) is partially documented in [/sama/v2 §1.1](/sama/v2#11-layers), partially in the `/sama/sorted` discipline page, and partially encoded in the source code itself. A new contributor has to triangulate. | |
| 28 | +- **The layer-prefix convention** (`a31_*` = Layer 0, `b32_*` = Layer 1, `c14_*` = Layer 2, `d21_*` = Layer 3) is partially documented in [/sama/v2 §1.1](/sama/v2#11-layers), partially in the `/sama/discipline/sorted` discipline page, and partially encoded in the source code itself. A new contributor has to triangulate. | |
| 29 | 29 | |
| 30 | 30 | - **The branch-PR-deploy flow** (branch → `gh pr create` → merge → push p620 → `flatpak-spawn --host scripts/p620/deploy-tdd-md.sh` → live-verify) is in *zero* blog posts as a standalone procedure. It's mentioned as a constraint inside individual `/goal` bodies, embedded in PR commit messages, scattered across the recent /goals archive. Never collected. |
| 31 | 31 | |
content/home.md
+4
−4
| @@ -20,10 +20,10 @@ The five §5 core metrics — **graphDepth · fanByLayer · boundaryRatio · wor | ||
| 20 | 20 | |
| 21 | 21 | ## The four pillars |
| 22 | 22 | |
| 23 | -- **[S — Sorted.](/sama/sorted)** Lexicographic file order equals import direction. The dependency graph is the file tree. | |
| 24 | -- **[A — Architecture.](/sama/architecture)** Every file's prefix maps to one layer with explicit allowed/forbidden contents. No rogue files. | |
| 25 | -- **[M — Modeled.](/sama/modeled)** Every behavior file has a sibling test. Every external input is parsed at the boundary, never cast. | |
| 26 | -- **[A — Atomic.](/sama/atomic)** Files cap at ~700 lines. Split per domain, never via barrel re-exports. | |
| 23 | +- **[S — Sorted.](/sama/discipline/sorted)** Lexicographic file order equals import direction. The dependency graph is the file tree. | |
| 24 | +- **[A — Architecture.](/sama/discipline/architecture)** Every file's prefix maps to one layer with explicit allowed/forbidden contents. No rogue files. | |
| 25 | +- **[M — Modeled.](/sama/discipline/modeled)** Every behavior file has a sibling test. Every external input is parsed at the boundary, never cast. | |
| 26 | +- **[A — Atomic.](/sama/discipline/atomic)** Files cap at ~700 lines. Split per domain, never via barrel re-exports. | |
| 27 | 27 | |
| 28 | 28 | ## SAMA in your agent-coding stack |
| 29 | 29 | |
content/sama/architecture.md
+1
−1
| @@ -72,4 +72,4 @@ When you spot a violation, the fix is always the same shape: split the file alon | ||
| 72 | 72 | |
| 73 | 73 | --- |
| 74 | 74 | |
| 75 | -[← S — Sorted](/sama/sorted) · [/sama](/sama) · [next: M — Modeled →](/sama/modeled) | |
| 75 | +[← S — Sorted](/sama/discipline/sorted) · [/sama](/sama) · [next: M — Modeled →](/sama/discipline/modeled) | |
content/sama/atomic.md
+1
−1
| @@ -97,4 +97,4 @@ It also keeps **token cost** bounded. An agent given "edit `c51_render_reports.t | ||
| 97 | 97 | |
| 98 | 98 | --- |
| 99 | 99 | |
| 100 | -[← M — Modeled](/sama/modeled) · [/sama](/sama) · [back to the four properties](/sama) | |
| 100 | +[← M — Modeled](/sama/discipline/modeled) · [/sama](/sama) · [back to the four properties](/sama) | |
content/sama/modeled.md
+1
−1
| @@ -102,4 +102,4 @@ The fix: move the type to `c31_project_config.ts`, write a `parseProjectConfig` | ||
| 102 | 102 | |
| 103 | 103 | --- |
| 104 | 104 | |
| 105 | -[← A — Architecture](/sama/architecture) · [/sama](/sama) · [next: A — Atomic →](/sama/atomic) | |
| 105 | +[← A — Architecture](/sama/discipline/architecture) · [/sama](/sama) · [next: A — Atomic →](/sama/discipline/atomic) | |
content/sama/sorted.md
+1
−1
| @@ -73,4 +73,4 @@ A file tree where: | ||
| 73 | 73 | |
| 74 | 74 | --- |
| 75 | 75 | |
| 76 | -[← /sama](/sama) · [next: A — Architecture →](/sama/architecture) | |
| 76 | +[← /sama](/sama) · [next: A — Architecture →](/sama/discipline/architecture) | |
src/a31_blog.ts
+1
−1
| @@ -15,7 +15,7 @@ export const ALL_POSTS: BlogEntry[] = [ | ||
| 15 | 15 | { |
| 16 | 16 | slug: "sama-v2-on-ramp-gap", |
| 17 | 17 | title: "Every artifact has a URL. The on-ramp doesn't.", |
| 18 | - description: "Open tdd.md in a fresh browser. Count the entry points — /sama/v2 (spec), /sama/v2/verify (live), /blog (narrative), /goals (contracts), /GIT/tdd.md (source), /sitemap.xml (every URL). Six artifacts, each auditable, each its own blog post subject. Now ask the question the site exists to answer for a reader: 'how do I contribute?' There is no answer. No CONTRIBUTING.md, no /contributing URL, no canonical on-ramp anywhere. Forty PRs of preaching auditability — and the most basic on-ramp test failing the entire time. This post is the drama. Walks the artifact-vs-path table: every OUTPUT lives in a canonical place (✓), every authoring rule lives somewhere a contributor wouldn't think to look (the /goal workflow rule is in an agent-private memory file that humans literally cannot read; the layer convention is in /sama/v2 §1.1 + /sama/sorted + the source tree; the branch-PR-deploy flow is scattered across PR commit messages and individual /goal bodies; the image convention lives in memory only; the Containerfile gotcha lives in one PR's commit message). Concrete 10-step thought experiment of what a new contributor adding their first blog post would actually have to do — ends in 'give up, open an issue saying how do I contribute'. Frames this as a SAMA v2 self-violation parallel to /blog/sama-v2-goal-chain-gap one level up: that post fixed the /goal in the chain; this post argues the workflow itself is the next missing artifact, same structural failure applied to procedure instead of contract. Sketches the fix: one CONTRIBUTING.md, two surfaces (./CONTRIBUTING.md at repo root + /contributing on the site, same markdown source), with three load-bearing properties — single source of truth (links don't restate), dogfooded (its own creation is /goal-driven at /goals/contributing-md), drift-detectable (a test that grep-checks CONTRIBUTING.md contains only references not restatements). The /goal that drives the implementation follows this post per the plan-execute-postmortem pattern.", | |
| 18 | + description: "Open tdd.md in a fresh browser. Count the entry points — /sama/v2 (spec), /sama/v2/verify (live), /blog (narrative), /goals (contracts), /GIT/tdd.md (source), /sitemap.xml (every URL). Six artifacts, each auditable, each its own blog post subject. Now ask the question the site exists to answer for a reader: 'how do I contribute?' There is no answer. No CONTRIBUTING.md, no /contributing URL, no canonical on-ramp anywhere. Forty PRs of preaching auditability — and the most basic on-ramp test failing the entire time. This post is the drama. Walks the artifact-vs-path table: every OUTPUT lives in a canonical place (✓), every authoring rule lives somewhere a contributor wouldn't think to look (the /goal workflow rule is in an agent-private memory file that humans literally cannot read; the layer convention is in /sama/v2 §1.1 + /sama/discipline/sorted + the source tree; the branch-PR-deploy flow is scattered across PR commit messages and individual /goal bodies; the image convention lives in memory only; the Containerfile gotcha lives in one PR's commit message). Concrete 10-step thought experiment of what a new contributor adding their first blog post would actually have to do — ends in 'give up, open an issue saying how do I contribute'. Frames this as a SAMA v2 self-violation parallel to /blog/sama-v2-goal-chain-gap one level up: that post fixed the /goal in the chain; this post argues the workflow itself is the next missing artifact, same structural failure applied to procedure instead of contract. Sketches the fix: one CONTRIBUTING.md, two surfaces (./CONTRIBUTING.md at repo root + /contributing on the site, same markdown source), with three load-bearing properties — single source of truth (links don't restate), dogfooded (its own creation is /goal-driven at /goals/contributing-md), drift-detectable (a test that grep-checks CONTRIBUTING.md contains only references not restatements). The /goal that drives the implementation follows this post per the plan-execute-postmortem pattern.", | |
| 19 | 19 | date: "2026-05-25", |
| 20 | 20 | }, |
| 21 | 21 | { |
src/b32_edit_resolve.test.ts
+1
−1
| @@ -4,7 +4,7 @@ import { resolveEdit } from "./b32_edit_resolve.ts"; | ||
| 4 | 4 | test("resolves an existing sama page", () => { |
| 5 | 5 | const r = resolveEdit("sama", "sorted"); |
| 6 | 6 | expect(r).not.toBeNull(); |
| 7 | - expect(r?.pageUrl).toBe("/sama/sorted"); | |
| 7 | + expect(r?.pageUrl).toBe("/sama/discipline/sorted"); | |
| 8 | 8 | expect(r?.filePath).toBe("content/sama/sorted.md"); |
| 9 | 9 | expect(r?.title).toMatch(/Sorted/); |
| 10 | 10 | }); |
src/b32_edit_resolve.ts
+7
−1
| @@ -58,10 +58,16 @@ export const resolveEdit = (section: string, slug: string): ResolvedEdit | null | ||
| 58 | 58 | if (!SAFE_SLUG.test(slug)) return null; |
| 59 | 59 | const title = lookupTitle(section, slug); |
| 60 | 60 | if (title === null) return null; |
| 61 | + // /sama discipline pages live under /sama/discipline/<slug> as of | |
| 62 | + // PR #53; other sections keep the /<section>/<slug> shape. | |
| 63 | + const pageUrl = | |
| 64 | + section === "sama" && slug !== "skill" && slug !== "v2" | |
| 65 | + ? `/sama/discipline/${slug}` | |
| 66 | + : `/${section}/${slug}`; | |
| 61 | 67 | return { |
| 62 | 68 | section, |
| 63 | 69 | slug, |
| 64 | - pageUrl: `/${section}/${slug}`, | |
| 70 | + pageUrl, | |
| 65 | 71 | filePath: `content/${section}/${slug}.md`, |
| 66 | 72 | title, |
| 67 | 73 | }; |
src/b32_sama_discipline_url_redirect.test.ts
+55
−0
| @@ -0,0 +1,55 @@ | ||
| 1 | +import { describe, expect, test } from "bun:test"; | |
| 2 | +import { rewriteOldSamaDisciplineUrl } from "./b32_sama_discipline_url_redirect.ts"; | |
| 3 | + | |
| 4 | +describe("rewriteOldSamaDisciplineUrl", () => { | |
| 5 | + test("rewrites /sama/sorted", () => { | |
| 6 | + expect(rewriteOldSamaDisciplineUrl("/sama/sorted")).toBe( | |
| 7 | + "/sama/discipline/sorted", | |
| 8 | + ); | |
| 9 | + }); | |
| 10 | + | |
| 11 | + test("rewrites /sama/architecture", () => { | |
| 12 | + expect(rewriteOldSamaDisciplineUrl("/sama/architecture")).toBe( | |
| 13 | + "/sama/discipline/architecture", | |
| 14 | + ); | |
| 15 | + }); | |
| 16 | + | |
| 17 | + test("rewrites /sama/modeled", () => { | |
| 18 | + expect(rewriteOldSamaDisciplineUrl("/sama/modeled")).toBe( | |
| 19 | + "/sama/discipline/modeled", | |
| 20 | + ); | |
| 21 | + }); | |
| 22 | + | |
| 23 | + test("rewrites /sama/atomic", () => { | |
| 24 | + expect(rewriteOldSamaDisciplineUrl("/sama/atomic")).toBe( | |
| 25 | + "/sama/discipline/atomic", | |
| 26 | + ); | |
| 27 | + }); | |
| 28 | + | |
| 29 | + test("returns null for new-form URLs", () => { | |
| 30 | + expect(rewriteOldSamaDisciplineUrl("/sama/discipline/sorted")).toBe(null); | |
| 31 | + }); | |
| 32 | + | |
| 33 | + test("returns null for /sama landing", () => { | |
| 34 | + expect(rewriteOldSamaDisciplineUrl("/sama")).toBe(null); | |
| 35 | + }); | |
| 36 | + | |
| 37 | + test("returns null for /sama/v2 and its subpaths", () => { | |
| 38 | + expect(rewriteOldSamaDisciplineUrl("/sama/v2")).toBe(null); | |
| 39 | + expect(rewriteOldSamaDisciplineUrl("/sama/v2/verify")).toBe(null); | |
| 40 | + }); | |
| 41 | + | |
| 42 | + test("returns null for /sama/skill", () => { | |
| 43 | + expect(rewriteOldSamaDisciplineUrl("/sama/skill")).toBe(null); | |
| 44 | + }); | |
| 45 | + | |
| 46 | + test("returns null for invented discipline slugs", () => { | |
| 47 | + expect(rewriteOldSamaDisciplineUrl("/sama/syntropic")).toBe(null); | |
| 48 | + }); | |
| 49 | + | |
| 50 | + test("returns null for non-/sama paths", () => { | |
| 51 | + expect(rewriteOldSamaDisciplineUrl("/blog/foo")).toBe(null); | |
| 52 | + expect(rewriteOldSamaDisciplineUrl("/")).toBe(null); | |
| 53 | + expect(rewriteOldSamaDisciplineUrl("")).toBe(null); | |
| 54 | + }); | |
| 55 | +}); | |
src/b32_sama_discipline_url_redirect.ts
+15
−0
| @@ -0,0 +1,15 @@ | ||
| 1 | +// b32 — Layer 1 pure helper: rewrite the legacy /sama/<discipline> | |
| 2 | +// URL shape to /sama/discipline/<discipline>. Second instance of the | |
| 3 | +// b32_<old>_url_redirect pattern established by b32_git_url_redirect.ts | |
| 4 | +// — same shape, same sibling-test structure, same Layer-3 wrapper | |
| 5 | +// in d21_handlers_fallback. The four discipline slugs are spec-frozen | |
| 6 | +// (§0 SAMA = Sorted, Architecture, Modeled, Atomic), so the regex | |
| 7 | +// hard-codes the enumeration rather than accepting any kebab slug. | |
| 8 | + | |
| 9 | +const OLD_PATTERN = /^\/sama\/(sorted|architecture|modeled|atomic)$/; | |
| 10 | + | |
| 11 | +export const rewriteOldSamaDisciplineUrl = (pathname: string): string | null => { | |
| 12 | + const m = OLD_PATTERN.exec(pathname); | |
| 13 | + if (m === null) return null; | |
| 14 | + return `/sama/discipline/${m[1]}`; | |
| 15 | +}; | |
src/b51_render_docs_layout.ts
+1
−1
| @@ -18,7 +18,7 @@ import { | ||
| 18 | 18 | } from "./b51_render_layout.ts"; |
| 19 | 19 | |
| 20 | 20 | export interface DocsPageOptions extends Omit<PageOptions, "bodyHtml"> { |
| 21 | - // The route path the user is on, e.g. "/sama/sorted". Used to | |
| 21 | + // The route path the user is on, e.g. "/sama/discipline/sorted". Used to | |
| 22 | 22 | // compute prev/next. |
| 23 | 23 | pathForDocs: string; |
| 24 | 24 | // Optional override of which file the "edit on GitHub" link |
src/d21_app.ts
+2
−2
| @@ -202,7 +202,7 @@ export const createApp = (port: number) => Bun.serve({ | ||
| 202 | 202 | lastmod: p.date, |
| 203 | 203 | })); |
| 204 | 204 | const samaUrls: SitemapUrl[] = ALL_SAMA.map((d) => ({ |
| 205 | - loc: `${SITE_BASE_URL}/sama/${d.slug}`, | |
| 205 | + loc: `${SITE_BASE_URL}/sama/discipline/${d.slug}`, | |
| 206 | 206 | })); |
| 207 | 207 | const guideUrls: SitemapUrl[] = ALL_GUIDES.map((g) => ({ |
| 208 | 208 | loc: `${SITE_BASE_URL}/guides/${g.slug}`, |
| @@ -415,7 +415,7 @@ ${rows} | ||
| 415 | 415 | |
| 416 | 416 | "/sama": samaLandingHandler, |
| 417 | 417 | |
| 418 | - "/sama/:slug": samaSlugHandler, | |
| 418 | + "/sama/discipline/:slug": samaSlugHandler, | |
| 419 | 419 | |
| 420 | 420 | "/goals": goalsLandingHandler, |
| 421 | 421 | |
src/d21_handlers_fallback.ts
+17
−0
| @@ -23,6 +23,7 @@ import { | ||
| 23 | 23 | repoBrowseHandler, |
| 24 | 24 | } from "./d21_handlers_repo_browse.ts"; |
| 25 | 25 | import { rewriteOldGitUrl } from "./b32_git_url_redirect.ts"; |
| 26 | +import { rewriteOldSamaDisciplineUrl } from "./b32_sama_discipline_url_redirect.ts"; | |
| 26 | 27 | |
| 27 | 28 | const isGitProtocol = (pathname: string, search: URLSearchParams): boolean => { |
| 28 | 29 | if (pathname.includes(".git/") || pathname.endsWith(".git")) return true; |
| @@ -138,6 +139,22 @@ export const appFetch = async (req: Request): Promise<Response> => { | ||
| 138 | 139 | }); |
| 139 | 140 | } |
| 140 | 141 | |
| 142 | + // Legacy /sama/<discipline> URLs permanent-redirect to the new | |
| 143 | + // /sama/discipline/<slug> namespace. Same pattern-as-redirect shape | |
| 144 | + // as the /GIT/ block above — pure Layer-1 transform + Layer-3 | |
| 145 | + // Response wrapper. Hypothesis-test instance of the pattern's | |
| 146 | + // reusability (see /blog/sama-v2-git-url-refactor-postmortem). | |
| 147 | + const newSamaPath = rewriteOldSamaDisciplineUrl(url.pathname); | |
| 148 | + if (newSamaPath !== null) { | |
| 149 | + return new Response(null, { | |
| 150 | + status: 301, | |
| 151 | + headers: { | |
| 152 | + Location: newSamaPath, | |
| 153 | + "Cache-Control": "public, max-age=86400", | |
| 154 | + }, | |
| 155 | + }); | |
| 156 | + } | |
| 157 | + | |
| 141 | 158 | // SAMA-native repo browse at /GIT/:repo/{tree,blob,raw}/:ref/<path>. |
| 142 | 159 | // The wildcard path needs more flexibility than Bun's :param routes |
| 143 | 160 | // give us (no slashes), so we match in the fallback fetch instead. |
src/d21_handlers_sama.ts
+5
−5
| @@ -431,10 +431,10 @@ The four discipline pages below are the v1 practitioner-facing version of SAMA. | ||
| 431 | 431 | ### reading order |
| 432 | 432 | |
| 433 | 433 | If you're new to this: |
| 434 | -1. Start with **[Sorted](/sama/sorted)** — it has the verification grep that everything else is built around. | |
| 435 | -2. Then **[Architecture](/sama/architecture)** — what each layer prefix means. | |
| 436 | -3. Then **[Modeled](/sama/modeled)** — where types and tests live. | |
| 437 | -4. Then **[Atomic](/sama/atomic)** — the split rule that keeps the rest honest as the codebase grows. | |
| 434 | +1. Start with **[Sorted](/sama/discipline/sorted)** — it has the verification grep that everything else is built around. | |
| 435 | +2. Then **[Architecture](/sama/discipline/architecture)** — what each layer prefix means. | |
| 436 | +3. Then **[Modeled](/sama/discipline/modeled)** — where types and tests live. | |
| 437 | +4. Then **[Atomic](/sama/discipline/atomic)** — the split rule that keeps the rest honest as the codebase grows. | |
| 438 | 438 | |
| 439 | 439 | Each page is short, opinionated, and ends with the common mistakes you'll see if the discipline lapses. |
| 440 | 440 | |
| @@ -539,7 +539,7 @@ export const samaSlugHandler = async (req: { params: { slug: string } }): Promis | ||
| 539 | 539 | } |
| 540 | 540 | const rawMd = await file.text(); |
| 541 | 541 | // Stripe-style "older version" banner — prepended to every v1 |
| 542 | - // discipline page so a reader landing directly on /sama/sorted etc. | |
| 542 | + // discipline page so a reader landing directly on /sama/discipline/sorted etc. | |
| 543 | 543 | // sees the v2-spec pointer before the v1 prose. |
| 544 | 544 | const v1Banner = `> **You are reading the v1 practitioner version of this property.** The formal, normative v2 specification — frozen core, profile mechanism, deterministic verifier, and §5 cross-repo measurement chain — lives at **[/sama/v2](/sama/v2)**. This page is preserved as background reading.\n\n`; |
| 545 | 545 | const md = v1Banner + rawMd; |