Site structure: tiered doctrine hierarchy (DESIGN, not yet applied)
Status: APPLIED (2026-10-10, tags
pre-tier-migration→tier-migration-done). The corrections found while applying it are in §9.Original status: design only. No files had moved yet. The plan follows triage.md as approved: F9 is “Christ’s saving death”, substitution and satisfaction are S20, and the tier weights are 3 / 2 / 1. Every one of the 50 existing questions is mapped below: the original 37 plus 13 from scripture-and-tradition, trinity, christology, other-sacraments and church-authority, as of
51b92d3.
0. Naming rules (avoids ID collisions)
The old IDs overlap with the new ones. For example, salvation S1–S4 collide with doctrines S1–S20, trinity T1–T2 collide with doctrines T1–T18, and the father-era tiers T1–T4 collide with both. So:
| Thing | Format | Example |
|---|---|---|
| Doctrine ID | tier letter + 2 digits | F09, S04, T10 (displayed as F9, S4, T10) |
| Question ID (new, canonical) | doctrine ID + . + n | S04.1 |
| Legacy question code | kept forever as an alias, always written with its old category in prose | eucharist/E1, salvation/S3 |
| Father-era tiers | renamed Era I–IV (was T1–T4) | Era I = to c. 150 |
| Tier | first-order / second-order / third-order (data: 1 / 2 / 3) |
1. The tree
Tier → doctrine (one page each) → subtopics (H2 sections) → questions (verdict rows and anchors). [new] means no research yet; the page is created as a stub marked “not yet researched”. Questions in bold are existing ones (new ID ← legacy code).
First-order (weight 3)
├─ F01 The Trinity
│ └─ One God in three persons ............. F01.1 ← trinity/T1
├─ F02 Deity of Christ ......................... [new]
├─ F03 True humanity of Christ ................. [new]
├─ F04 One person, God and man
│ └─ The union of natures ................. F04.1 ← christology/X1
├─ F05 Virgin birth / incarnation .............. [new]
├─ F06 Deity and personhood of the Holy Spirit . [new] (draws on trinity notes)
├─ F07 Creation out of nothing ................. [new]
├─ F08 Sin and the need for grace
│ ├─ The fall ............................. F08.1 ← original-sin/O1
│ └─ Grace before faith ................... F08.2 ← salvation/S1
├─ F09 Christ's saving death ................... [new question to add: F09.1 "Did Christ's death save?"; A1–A3 are context]
├─ F10 Bodily resurrection of Christ ........... [new]
├─ F11 Ascension and session ................... [new]
├─ F12 Salvation by grace through faith in Christ
│ └─ Gift, not wage ....................... F12.1 ← justification/Q2
├─ F13 Return of Christ and final judgement .... [new]
├─ F14 General resurrection and eternal life
│ └─ Resurrection of the flesh ............ F14.1 ← afterlife/L4
├─ F15 Authority and truthfulness of Scripture . [new] (C1 cross-linked)
└─ F16 One holy catholic and apostolic Church .. [new]
Second-order (weight 2)
├─ S01 Nature of justification
│ ├─ What "justify" means ................. S01.1 ← justification/Q1
│ ├─ Augustine's account .................. S01.2 ← justification/Q3
│ ├─ Forgiveness, baptism and later sin ... S01.3 ← justification/Q4
│ ├─ Faith and works (James) .............. S01.4 ← justification/Q5
│ └─ Exchange and imputation .............. S01.5 ← justification/Q6
├─ S02 Baptism: subjects
│ └─ Infant baptism ....................... S02.1 ← baptism/B2
├─ S03 Baptism: effect
│ ├─ Regeneration ......................... S03.1 ← baptism/B1
│ └─ Necessity ............................ S03.2 ← baptism/B4
├─ S04 Eucharist: presence
│ ├─ Real presence ........................ S04.1 ← eucharist/E1
│ └─ Change and mode ...................... S04.2 ← eucharist/E3
├─ S05 Eucharist: sacrifice ................. S05.1 ← eucharist/E2
├─ S06 Number and nature of sacraments
│ ├─ Chrismation / confirmation ........... S06.1 ← other-sacraments/K1
│ ├─ Confession / penance ................. S06.2 ← other-sacraments/K2
│ ├─ Anointing of the sick ................ S06.3 ← other-sacraments/K3
│ └─ Marriage ............................. S06.4 ← other-sacraments/K4
├─ S07 Scripture and tradition
│ ├─ Supreme and sufficient norm .......... S07.1 ← scripture-and-tradition/C1
│ ├─ Canon ................................ S07.2 ← scripture-and-tradition/C2
│ └─ Rule of faith and interpretation ..... S07.3 ← scripture-and-tradition/C3
├─ S08 Papal primacy and infallibility
│ ├─ Peter and Rome ....................... S08.1 ← church-authority/P1
│ └─ Universal jurisdiction ............... S08.2 ← church-authority/P2
├─ S09 Church government ....................... [new]
├─ S10 Ordination to the presbyterate (classic positions) [new]
├─ S11 Predestination and election
│ ├─ Free choice .......................... S11.1 ← salvation/S2
│ └─ Election ............................. S11.2 ← salvation/S3
├─ S12 Extent of the atonement ................. [new]
├─ S13 Perseverance ......................... S13.1 ← salvation/S4
├─ S14 Original sin: guilt or corruption
│ ├─ What is inherited .................... S14.1 ← original-sin/O2
│ ├─ Romans 5:12 (eph' hō) ................ S14.2 ← original-sin/O3
│ └─ East and West compared ............... S14.3 ← original-sin/O4
├─ S15 Filioque ............................. S15.1 ← trinity/T2
├─ S16 Chalcedonian vs miaphysite wording
│ ├─ Natures formula ...................... S16.1 [new; X1 notes feed it]
│ └─ Two wills (Constantinople III) ....... S16.2 ← christology/X2
├─ S17 Purgatory and prayers for the dead
│ ├─ Prayers for the dead ................. S17.1 ← afterlife/L2
│ └─ Purification after death ............. S17.2 ← afterlife/L3
├─ S18 Invocation of saints and Marian dogmas .. [new]
├─ S19 Veneration of icons and relics
│ ├─ Relics: honour ....................... S19.1 ← relics/R1
│ ├─ Relics: honour vs worship ............ S19.2 ← relics/R2
│ ├─ Relics: miracles ..................... S19.3 ← relics/R3
│ ├─ Relics: the objection ................ S19.4 ← relics/R4
│ ├─ Icons: are images allowed ............ S19.5 ← icons/I1
│ ├─ Icons: proskynesis vs latreia ........ S19.6 ← icons/I2
│ └─ Icons: early opposition .............. S19.7 ← icons/I3
└─ S20 Penal substitution and satisfaction
├─ Substitution ......................... S20.1 ← atonement/A3
└─ Satisfaction of divine law ........... S20.2 ← atonement/A4
Third-order (weight 1)
├─ T01 Millennium ......................... [new]
├─ T02 Timing of end-times events ......... [new]
├─ T03 Age of the earth / days of creation [new]
├─ T04 Mode of baptism .................... T04.1 ← baptism/B3
├─ T05 Frequency of communion ............. [new]
├─ T06 Atonement models beyond the core
│ ├─ Victory and the ransom question .... T06.1 ← atonement/A1
│ └─ Recapitulation ..................... T06.2 ← atonement/A2
├─ T07 Order of decrees ................... [new]
├─ T08 Origin of the soul ................. [new]
├─ T09 Intermediate state ................. T09.1 ← afterlife/L1
├─ T10 Final punishment (ECT, annihilation) T10.1 ← afterlife/L5
├─ T11 Universal restoration as a hope .... [new; L5 Nyssa material cross-linked]
├─ T12 Spiritual gifts .................... [new]
├─ T13 Sabbath / Lord's Day ............... [new]
├─ T14 Regulative vs normative worship .... [new]
├─ T15 Liturgical calendar ................ [new]
├─ T16 Clerical celibacy .................. [new]
├─ T17 Fasting rules ...................... [new]
└─ T18 Theosis vocabulary ................. [new]Coverage at migration: 50 questions spread over 25 doctrines with research (5 first-order, 16 second-order, 4 third-order). That leaves 29 doctrines as stubs, including F09, which needs one new question (F09.1). The earlier draft said 27 doctrines; that count was wrong.
2. Mapping table (all 50 existing questions)
| Legacy | New ID | Doctrine | Legacy | New ID | Doctrine |
|---|---|---|---|---|---|
| justification/Q1 | S01.1 | Nature of justification | relics/R1 | S19.1 | Icons and relics |
| justification/Q2 | F12.1 | Grace through faith | relics/R2 | S19.2 | Icons and relics |
| justification/Q3 | S01.2 | Nature of justification | relics/R3 | S19.3 | Icons and relics |
| justification/Q4 | S01.3 | Nature of justification | relics/R4 | S19.4 | Icons and relics |
| justification/Q5 | S01.4 | Nature of justification | icons/I1 | S19.5 | Icons and relics |
| justification/Q6 | S01.5 | Nature of justification | icons/I2 | S19.6 | Icons and relics |
| baptism/B1 | S03.1 | Baptism: effect | icons/I3 | S19.7 | Icons and relics |
| baptism/B2 | S02.1 | Baptism: subjects | afterlife/L1 | T09.1 | Intermediate state |
| baptism/B3 | T04.1 | Mode of baptism | afterlife/L2 | S17.1 | Purgatory, prayers |
| baptism/B4 | S03.2 | Baptism: effect | afterlife/L3 | S17.2 | Purgatory, prayers |
| salvation/S1 | F08.2 | Sin and grace | afterlife/L4 | F14.1 | General resurrection |
| salvation/S2 | S11.1 | Predestination | afterlife/L5 | T10.1 | Final punishment |
| salvation/S3 | S11.2 | Predestination | christology/X1 | F04.1 | One person |
| salvation/S4 | S13.1 | Perseverance | christology/X2 | S16.2 | Chalcedon vs miaphysite |
| eucharist/E1 | S04.1 | Eucharist: presence | church-authority/P1 | S08.1 | Papacy |
| eucharist/E2 | S05.1 | Eucharist: sacrifice | church-authority/P2 | S08.2 | Papacy |
| eucharist/E3 | S04.2 | Eucharist: presence | scripture-and-tradition/C1 | S07.1 | Scripture and tradition |
| atonement/A1 | T06.1 | Atonement models | scripture-and-tradition/C2 | S07.2 | Scripture and tradition |
| atonement/A2 | T06.2 | Atonement models | scripture-and-tradition/C3 | S07.3 | Scripture and tradition |
| atonement/A3 | S20.1 | Substitution | trinity/T1 | F01.1 | Trinity |
| atonement/A4 | S20.2 | Substitution | trinity/T2 | S15.1 | Filioque |
| original-sin/O1 | F08.1 | Sin and grace | other-sacraments/K1 | S06.1 | Sacraments |
| original-sin/O2 | S14.1 | Original sin | other-sacraments/K2 | S06.2 | Sacraments |
| original-sin/O3 | S14.2 | Original sin | other-sacraments/K3 | S06.3 | Sacraments |
| original-sin/O4 | S14.3 | Original sin | other-sacraments/K4 | S06.4 | Sacraments |
Judgement calls for Cameron to check:
- Q2 → F12. “No works precede initial justification” is the gospel core shared by all nine traditions. Q1, Q3–Q6 are about how justification works, which is second-order.
- S1 and O1 → F08. Anti-Pelagian grace and a real fall are first-order. O2–O4 and S2–S3 are second-order.
- C1 → S07. It is about how Scripture ranks against tradition, so it goes to S07. F15 itself still needs a short new question.
- X2 → S16. Dyothelitism is exactly what separates the Chalcedonian churches from the miaphysites.
3. Directory layout
The current mounts stay the same. One new tree, doctrines/, is mounted to content/doctrines. The old categories/ folder goes after migration, except for redirect stubs.
doctrines/
_index.md # tier overview (the hierarchy hub)
first-order/
_index.md # tier page: definition, weight 3, doctrine cards
f01-trinity/
index.md # doctrine page: summary, subtopics, verdict table, post-750, traditions' own tiers
reasoning.md # was notes/reasoning/trinity.md (only the parts for F01)
f04-one-person/ ...
second-order/ ...
third-order/ ...
notes/sources/*.md # UNCHANGED: father notes are shared across doctrines
notes/scripture/*.md # UNCHANGED
notes/reasoning/<legacy>.md # kept as frozen archives with legacy anchors (see §5), banner "moved"
categories/<legacy>.md # replaced by redirect stubs (see §4)
data/doctrines.yaml # GENERATED: tier → doctrine → questions registry
data/legacy_map.yaml # hand-maintained: legacy code → new ID (the table in §2)
data/closeness.yaml # GENERATED (extended, §6)
data/timeline/<doctrine-id>.yaml # GENERATED, one per doctrine
scripts/scripture_scores.json # rekeyed by new question ID (legacy key kept as "legacy")
scripts/emphasis.json # NEW: shaped-by emphasis 0–2 per tradition per doctrine, with citationFront matter
Tier page (second-order/_index.md):
title: "Second-order doctrines"
tier: 2
tier_weight: 2 # scoring weight (3/2/1)
weight: 2 # Hugo sidebar order (do not confuse with tier_weight)Doctrine page (second-order/s04-eucharist-presence/index.md):
title: "Eucharist: presence"
doctrine: S04
tier: 2
parent: second-order
weight: 4 # sidebar order inside the tier = doctrine number
status: draft # draft | stub | verified
legacy_categories: [eucharist]
aliases: ["/categories/eucharist/"] # only on the PRIMARY successor of a legacy category
contested_tier: # traditions placing it elsewhere (triage §3), with cite
Catholic: {tier: 1, cite: "LG 11"}
Lutheran: {tier: 1, cite: "SA III.6 / FC VII (to verify)"}
questions:
- {id: S04.1, legacy: eucharist/E1, anchor: s04-1, legacy_anchor: e1}
- {id: S04.2, legacy: eucharist/E3, anchor: s04-2, legacy_anchor: e3}
last_updated: 2026-10-10The verdict table keeps its current column format, so the parser carries over. The only change is that the first cell becomes [S04.1](reasoning.md#s04-1).
4. URL scheme and redirects
| Page | URL |
|---|---|
| Hierarchy hub | /doctrines/ |
| Tier | /doctrines/second-order/ |
| Doctrine | /doctrines/second-order/s04-eucharist-presence/ |
| Question | /doctrines/second-order/s04-eucharist-presence/#s04-1 |
| Reasoning | /doctrines/second-order/s04-eucharist-presence/reasoning/#s04-1-step1 |
| Short link | /d/S04/ → doctrine; /q/S04.1/ → question (Cloudflare _redirects) |
The slug after the ID can be renamed without breaking links, because the short links go by ID only.
Redirects from the old URLs. Old categories split across several doctrines, so a single 301 would land readers in the wrong place for some questions. Instead:
- Server 301s in
site/static/_redirects(Cloudflare Pages). These cover whole-page moves where the target is unambiguous:/categories/→/doctrines/,/categories/christology/→ F04,/categories/trinity/→ F01,/categories/church-authority/→ S08,/categories/other-sacraments/→ S06,/categories/scripture-and-tradition/→ S07,/categories/eucharist/→ S04,/categories/icons/and/categories/relics/→ S19,/notes/reasoning/<x>/→ the same as its category. - Split categories keep a lightweight stub page instead of a 301: justification, baptism, salvation, atonement, original-sin and afterlife. Each stub lists where its questions went and runs a small inline script. If the URL has a fragment (
#b2,#b2-step3), the script looks it up inlegacy_map(rendered into the page as JSON) and doeslocation.replace()to the exact new anchor. Without JS, the readable list still works. - Old internal links in markdown are rewritten by the migration script (§8 step 5). The redirects are only there for links from outside the repo.
5. Anchors
- New canonical anchors:
#s04-1(question),#s04-1-step3(reasoning step),#doctrine-summary,#subtopic-<slug>. Source note anchors (#justin-martyr-bap-s1) are never renamed. They are shared across doctrines, and their topic infix is just an ID, not a location. - Aliases: every moved question and step heading gets both IDs. The canonical ID goes on the heading, and an empty
<span id="b1"></span>/<span id="b1-step3"></span>sits right before it. A new render hook would emit the alias spans from front-matterquestions[].legacy_anchor, so authors never type them. - Collision check: legacy anchors are only unique within their old page. Two of them could land on one new page, for example
s1from salvation and ans1alias. When that happens the alias becomes<legacy-category>-<code>(salvation-s1), and the stub redirect uses that form. The build script fails if any page ends up with a duplicate ID. docs/conventions.mdgets a “Stable anchors” section with these rules.
6. scripts/build_data.py and scoring
Input: walk doctrines/*/*/index.md instead of categories/*.md. Tier, doctrine ID and questions come from front matter, and verdict rows are parsed as now. ORDER is replaced by sorting on (tier, doctrine number). Legacy keys in scripture_scores.json and closeness_overrides.json are resolved through legacy_map.yaml, so both old and new keys work during the transition.
Per question: this stays as it is now. There is a 0–4 Scripture score per tradition, and patristic closeness with Era I–IV proximity weights (1.0 / 0.8 / 0.6 / 0.4, unchanged).
Tier weighting (most correct):
most_correct(t) = Σ_q w(tier_q)·s(t,q) / Σ_q w(tier_q)·4 w = {1:3, 2:2, 3:1}- Questions that are unscored for a tradition drop out of both sums, as now.
- Doctrine balancing: each doctrine’s questions are first averaged into a doctrine score, and the tier weight is applied per doctrine. Without this, S19’s 7 questions would count 7× while S05 has 1. The output includes both the balanced and per-question figures. Balanced is the default and is documented in
docs/scoring.md. - The same formula applies to the fathers lens and to the 50/50 average lens, so the home page keeps its three lenses, each tier-weighted.
In the faith: only the first-order doctrines that have research count.
in_faith(t) = mean over researched first-order doctrines of s(t,d)/4
band: "holds all" if every d ≥ 3; "with qualifications" if any d = 2; "denies" if any d ≤ 1
coverage = researched first-order doctrines / 16 (shown next to the band; band says "provisional" while < 16)- Each tradition also gets an
in_faith_by_own_tiersfigure, computed under itscontested_tierlist (triage §3 sensitivity view).
Shaped by:
distinct(t,d) = 1 if |s(t,d) − median_9(d)| ≥ 1.5, or the tradition is the sole holder of its position; else 0
emphasis(t,d) = 0–2 from scripts/emphasis.json (each with a confession-article citation)
shaped_by(t)[tier] = Σ_{d∈tier} emphasis·distinct / Σ_{all d} emphasis·distinct
top3(t) = the three highest emphasis·distinct doctrines- Doctrines with no research get a
distinctvalue from the confession texts alone, marked “confession-only”.
Output (data/closeness.yaml):
- Existing keys are kept for one release so the current shortcodes don’t break.
- New keys:
tiers[](id, weight, doctrines[] with scores, coverage, status)most_correct{lens: {trad: pct}}in_faith{trad: {pct, band, coverage, by_own_tiers}}shaped_by{trad: {first, second, third, top3}}sensitivity{trad: {default_rank, own_tier_rank}}
- The script also writes
data/doctrines.yamland fails the build if any legacy code is unmapped or any anchor is duplicated.
7. Home page
The order, top to bottom:
AI and academic-only disclaimer (unchanged).
“In the faith” strip: nine tradition chips with their band. Coverage is shown as e.g. “6 of 16 first-order doctrines researched; provisional”.
Most correct: the three lenses (Scripture, fathers, weighted average) with a working slider, all tier-weighted. A small “weights 3/2/1” note links to scoring.
The hierarchy: three tier columns (or stacked bands on mobile). Each doctrine card shows:
- its ID and title
- a one-line conclusion from
data/summaries.yaml - a status dot (researched / stub)
- a “contested tier” flag where a tradition disagrees
Stubs are greyed out, so you can see at a glance how much of the map is covered. This replaces
category-cards.Shaped by: one stacked bar per tradition, plus its top three distinctives.
Written overview: the existing per-topic prose, regrouped under tier headings.
A “Where tiers are contested” link to the sensitivity view.
The sidebar becomes Tier → Doctrine, with doctrine weight set by doctrine number.
8. Migration plan (run only once no one is committing)
Each step is a separate commit, and the site must build after every step.
Freeze check.
git pull --rebase. Confirm that no research process is running and thatcategories/still has the 50 questions (build_data.pyprints the count). Tagpre-tier-migration.Registry. Add
data/legacy_map.yaml(the table in §2) and a validation script,scripts/check_map.py. It checks that every verdict row incategories/*.mdis mapped exactly once.Scaffold. Create
doctrines/with the three tier_index.mdfiles and all 54 doctrineindex.mdfiles from a template. Doctrines with no research getstatus: stub. Add thehugo.tomlmount. Nothing is removed yet. Build.Move content (scripted,
scripts/migrate_tiers.py). For each legacy category:- split its verdict rows, post-750 sections and reasoning sections by question
- write them into the target doctrine’s
index.mdandreasoning.mdunder the right subtopic H2 - renumber the visible IDs to new IDs, and add
legacy_anchorentries - copy the shared plan, sources and method paragraphs into every target page in a collapsed “Origin” block, with a link to the archived file
The script is idempotent, so reruns overwrite and never append.
Rewrite links.
- Rewrite every repo-internal link
categories/x.md#…andnotes/reasoning/x.md#…to the new path and anchor through the map. - Leave
notes/sourceslinks as they are. - Run Hugo with
refLinksErrorLevel = "ERROR"and the existing link check. Zero broken links is required.
- Rewrite every repo-internal link
Rekey data. Rekey
scripture_scores.json,closeness_overrides.jsonanddata/summaries.yamlto the new IDs, keeping alegacyfield.Scoring. Update
build_data.pyas described in §6, and addscripts/emphasis.jsonas an empty skeleton (shaped-by shows “not yet assessed” until it is filled). Diff the old and new outputs. Untiered per-question Scripture totals must come out identical topre-tier-migration, which proves nothing was lost or double-counted. Then turn on tier weighting.Anchors and redirects.
- Add the alias render hook,
site/static/_redirects, and the stubs with fragment redirects for the split categories. - Archive
notes/reasoning/*.mdbehind a “moved” banner (their anchors still resolve). - Test a sample of 20 old URLs, with and without fragments, against the Cloudflare preview deploy.
- Add the alias render hook,
Home page. Add the new shortcodes (
in-faith,tier-map,shaped-by) and updateoverall-rankingandlensesto tier weighting. Fix and verify the average slider in a real browser on the preview URL, and removecategory-cards.Docs. Update
docs/conventions.md(new IDs, the stable-anchors section, Era I–IV),docs/scoring.md(tier weights, doctrine balancing, in-the-faith, shaped-by) andsite/CLOUDFLARE.md(the build now readsdoctrines/).Cleanup.
- Remove the old category bodies, keeping only the stubs.
- Remove the old output keys from
closeness.yaml. - Check the live solafide.dev after Cloudflare deploys.
- Tag
tier-migration-done.
Then research. Add F09.1, then fill the first-order stubs (F02, F03, F05, F06, F07, F10, F11, F13, F15, F16) in the order set in triage §5, so that “in the faith” stops being provisional.
Rollback: every step is a separate commit after the tag. Reverting to pre-tier-migration restores the flat site, and the old URLs never break, because the stubs and redirects are added before anything is removed.
9. As applied: corrections to this plan
- Branch bundles. Doctrine pages are
_index.md, notindex.md, soreasoning.mdcan be a page of its own. A leaf bundle would make it a resource. - Shared notes. The per-question reasoning moved to each doctrine’s
reasoning.md. The rest of each old reasoning file (preamble, post-750 development by tradition, andjustification-post750.md) moved tonotes/shared/<former-category>.md, with its anchors unchanged. The oldnotes/reasoning/files are moved-page stubs and are no longer kept as frozen copies. - 301s. A 301 is used only where every question of a category went to one doctrine: church-authority, icons, other-sacraments, relics and scripture-and-tradition. christology, trinity and eucharist split across two doctrines each, so they get stubs with fragment redirects, like the other split categories.
- Aliases. No Hugo
aliases:are used. Hugo alias pages would collide with the stubs, so the stubs and_redirectsdo that job. - Legacy anchors. These are written into the markdown by
scripts/migrate_tiers.py, not by a render hook. The effect is the same: authors don’t type them. - Plain-English summaries.
data/summaries.yamlis still keyed by the former category. Its text describes a whole former study, so it appears on doctrine pages as “From the study it came from”. The home overview uses each doctrine’s own verdicts until per-doctrine summaries are written. - Box builds. The box sandbox blocks Hugo’s CDN fetch of the FlexSearch script, so local test builds use
HUGO_PARAMS_SEARCH_ENABLE=false. Cloudflare builds with search on.