Skip to content
Structure

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:

ThingFormatExample
Doctrine IDtier letter + 2 digitsF09, S04, T10 (displayed as F9, S4, T10)
Question ID (new, canonical)doctrine ID + . + nS04.1
Legacy question codekept forever as an alias, always written with its old category in proseeucharist/E1, salvation/S3
Father-era tiersrenamed Era I–IV (was T1–T4)Era I = to c. 150
Tierfirst-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)

LegacyNew IDDoctrineLegacyNew IDDoctrine
justification/Q1S01.1Nature of justificationrelics/R1S19.1Icons and relics
justification/Q2F12.1Grace through faithrelics/R2S19.2Icons and relics
justification/Q3S01.2Nature of justificationrelics/R3S19.3Icons and relics
justification/Q4S01.3Nature of justificationrelics/R4S19.4Icons and relics
justification/Q5S01.4Nature of justificationicons/I1S19.5Icons and relics
justification/Q6S01.5Nature of justificationicons/I2S19.6Icons and relics
baptism/B1S03.1Baptism: effecticons/I3S19.7Icons and relics
baptism/B2S02.1Baptism: subjectsafterlife/L1T09.1Intermediate state
baptism/B3T04.1Mode of baptismafterlife/L2S17.1Purgatory, prayers
baptism/B4S03.2Baptism: effectafterlife/L3S17.2Purgatory, prayers
salvation/S1F08.2Sin and graceafterlife/L4F14.1General resurrection
salvation/S2S11.1Predestinationafterlife/L5T10.1Final punishment
salvation/S3S11.2Predestinationchristology/X1F04.1One person
salvation/S4S13.1Perseverancechristology/X2S16.2Chalcedon vs miaphysite
eucharist/E1S04.1Eucharist: presencechurch-authority/P1S08.1Papacy
eucharist/E2S05.1Eucharist: sacrificechurch-authority/P2S08.2Papacy
eucharist/E3S04.2Eucharist: presencescripture-and-tradition/C1S07.1Scripture and tradition
atonement/A1T06.1Atonement modelsscripture-and-tradition/C2S07.2Scripture and tradition
atonement/A2T06.2Atonement modelsscripture-and-tradition/C3S07.3Scripture and tradition
atonement/A3S20.1Substitutiontrinity/T1F01.1Trinity
atonement/A4S20.2Substitutiontrinity/T2S15.1Filioque
original-sin/O1F08.1Sin and graceother-sacraments/K1S06.1Sacraments
original-sin/O2S14.1Original sinother-sacraments/K2S06.2Sacraments
original-sin/O3S14.2Original sinother-sacraments/K3S06.3Sacraments
original-sin/O4S14.3Original sinother-sacraments/K4S06.4Sacraments

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 citation

Front 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-10

The 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

PageURL
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:

  1. 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.
  2. 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 in legacy_map (rendered into the page as JSON) and does location.replace() to the exact new anchor. Without JS, the readable list still works.
  3. 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-matter questions[].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 s1 from salvation and an s1 alias. 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.md gets 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_tiers figure, computed under its contested_tier list (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 distinct value 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.yaml and fails the build if any legacy code is unmapped or any anchor is duplicated.

7. Home page

The order, top to bottom:

  1. AI and academic-only disclaimer (unchanged).

  2. “In the faith” strip: nine tradition chips with their band. Coverage is shown as e.g. “6 of 16 first-order doctrines researched; provisional”.

  3. 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.

  4. 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.

  5. Shaped by: one stacked bar per tradition, plus its top three distinctives.

  6. Written overview: the existing per-topic prose, regrouped under tier headings.

  7. 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.

  1. Freeze check. git pull --rebase. Confirm that no research process is running and that categories/ still has the 50 questions (build_data.py prints the count). Tag pre-tier-migration.

  2. Registry. Add data/legacy_map.yaml (the table in §2) and a validation script, scripts/check_map.py. It checks that every verdict row in categories/*.md is mapped exactly once.

  3. Scaffold. Create doctrines/ with the three tier _index.md files and all 54 doctrine index.md files from a template. Doctrines with no research get status: stub. Add the hugo.toml mount. Nothing is removed yet. Build.

  4. 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.md and reasoning.md under the right subtopic H2
    • renumber the visible IDs to new IDs, and add legacy_anchor entries
    • 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.

  5. Rewrite links.

    • Rewrite every repo-internal link categories/x.md#… and notes/reasoning/x.md#… to the new path and anchor through the map.
    • Leave notes/sources links as they are.
    • Run Hugo with refLinksErrorLevel = "ERROR" and the existing link check. Zero broken links is required.
  6. Rekey data. Rekey scripture_scores.json, closeness_overrides.json and data/summaries.yaml to the new IDs, keeping a legacy field.

  7. Scoring. Update build_data.py as described in §6, and add scripts/emphasis.json as 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 to pre-tier-migration, which proves nothing was lost or double-counted. Then turn on tier weighting.

  8. Anchors and redirects.

    • Add the alias render hook, site/static/_redirects, and the stubs with fragment redirects for the split categories.
    • Archive notes/reasoning/*.md behind a “moved” banner (their anchors still resolve).
    • Test a sample of 20 old URLs, with and without fragments, against the Cloudflare preview deploy.
  9. Home page. Add the new shortcodes (in-faith, tier-map, shaped-by) and update overall-ranking and lenses to tier weighting. Fix and verify the average slider in a real browser on the preview URL, and remove category-cards.

  10. 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) and site/CLOUDFLARE.md (the build now reads doctrines/).

  11. 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.
  12. 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, not index.md, so reasoning.md can 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, and justification-post750.md) moved to notes/shared/<former-category>.md, with its anchors unchanged. The old notes/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 _redirects do 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.yaml is 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.