# BuiltToResist: Information Architecture for a 10,000+ Page Encyclopedia of Revolution and Resistance

## Architectural foundation

BuiltToResist should be designed as **three systems layered on top of one another**, rather than as one giant hierarchy:

1. **An encyclopedia** of stable pages written for people.
2. **A controlled knowledge graph** describing entities and their relationships.
3. **A research/evidence layer** recording sources, provenance, uncertainty, temporal scope, and competing interpretations.

That separation is the central design decision. A conventional category tree is too brittle for a subject in which one event can simultaneously belong to several countries, periods, ideological traditions, movements, institutional contexts, mechanisms, and scholarly debates. SKOS was explicitly designed for thesauri, taxonomies, classification schemes, subject headings, concept schemes, labels, broader/narrower relationships, associative relationships, and cross-vocabulary mappings; its model is therefore a strong foundation for BuiltToResist's controlled vocabularies, but it should not replace the richer factual graph. citeturn14view0turn14view1turn14view2

The recommended conceptual model is:

```text
                     ┌────────────────────────────┐
                     │       Human Web Page       │
                     │ title, prose, navigation   │
                     └─────────────┬──────────────┘
                                   │ describes
                                   ▼
                     ┌────────────────────────────┐
                     │           Entity           │
                     │ stable ID + canonical data │
                     └─────────────┬──────────────┘
                                   │
                 ┌─────────────────┼─────────────────┐
                 │                 │                 │
                 ▼                 ▼                 ▼
            Statement         Concept/Tag       External ID
        subject-predicate-   controlled term    Wikidata/DOI/
             object                              ISBN/VIAF/etc.
                 │
          ┌──────┴──────┐
          ▼             ▼
      Qualifiers     Citations
     time, place,    source + locator
     role, status,
     uncertainty
```

This “qualified statement” approach is particularly important for historical and political knowledge. Wikidata uses essentially the same architecture: a statement contains a property-value assertion and can carry qualifiers, references, and ranks so that the assertion can be contextualized rather than flattened into an unconditional fact. Its qualifier model explicitly supports temporal limits, methods, roles, and other contextual information that cannot be represented adequately in a bare subject-predicate-object triple. citeturn14view8turn14view9

For BuiltToResist, that means:

> **Do not store “Movement X → Ideology Y” as an unqualified truth whenever the real claim is “Movement X was characterized as Ideology Y by Source Z during Period T, with disputed status.”**

The database should be able to represent both.

### The most important modeling distinctions

**Page, entity, concept, and tag are not synonyms.**

A **page** is a publication object: something readers visit.

An **entity** is a thing BuiltToResist identifies independently: the French Revolution, Václav Havel, Solidarity, martial law, elite defection, a book, an archive collection, a research dataset.

A **concept** is a special kind of entity representing an idea or classification term: authoritarianism, general strike, regime defection, constitutional emergency powers, negotiated transition.

A **tag** is a controlled assignment of a concept to another entity or page for filtering or retrieval. The tag should normally point to a concept ID rather than exist as uncontrolled text.

A **category** is primarily a human-navigation construct: “Revolutions,” “Mechanisms,” “Primary Sources,” “Latin America.” It should not become a parallel factual ontology.

A **relationship** is an assertion between entities.

A **source** is evidence for a claim.

A **role** is contextual rather than necessarily an intrinsic entity type. “Leader,” “scholar,” and especially “primary source” fall into this category.

This distinction eliminates much of the taxonomy explosion that otherwise appears once the encyclopedia reaches thousands of pages.

### One ontology, multiple views

The site should have a relatively small set of underlying entity classes:

| Core entity family | Principal subtypes or roles |
|---|---|
| `HistoricalEvent` | Revolution, Uprising, Protest Wave, Strike Wave, Rebellion, Coup, Transition, Crackdown |
| `Movement` | Movement, Campaign, Coalition, Network |
| `Person` | Leader, Organizer, Scholar, Author, Official, Dissident |
| `Organization` | Party, Union, NGO, Military Unit, Government Body, Media Organization |
| `Institution` | Judiciary, Legislature, Monarchy, Security Apparatus, Electoral System, Emergency Regime |
| `Concept` | Ideology, Mechanism, Outcome Type, Legal Doctrine, Political Concept, Historical Era, Tactic |
| `Place/Polity` | Country, Historical State, Territory, Region, City |
| `CreativeWork` | Book, Article, Research Report, Primary-Source Document, Film, Recording, Photograph |
| `Collection` | Archive Collection, Bibliography, Timeline |
| `Analysis` | Case, Scenario, Question, Deep Analysis |
| `Dataset` | Statistical Dataset, Indicator Series, Corpus |
| `Observation` | Statistical observation; normally not a standalone page |

This is intentionally much smaller than the user's list of page families. The public information architecture can expose “Leaders,” “Books,” “Legal Doctrines,” “Statistics,” and similar categories, while the underlying model avoids creating a new fundamental object class every time a new navigation view is desired.

That pattern is consistent with schema.org's own inheritance strategy: broad types such as `Thing`, `CreativeWork`, `Event`, `Organization`, `Person`, and `Place` are specialized without requiring each website concept to become a wholly separate data architecture. citeturn13view13

For political-event data specifically, it is useful to keep the underlying **event** model separate from interpretive aggregate labels. ACLED, for example, treats the individual event as its fundamental unit and records actors, location, date, event type and subtype separately; users can then aggregate and filter across actors, geography, time, and type. BuiltToResist should not copy ACLED's event taxonomy, but the architectural lesson is valuable: store the underlying dimensions separately instead of putting every historical episode into one mutually exclusive folder. citeturn13view8

**Recommended principle:**

> **Ontology describes what things are.  
> Relationships describe how they connect.  
> Tags describe facets.  
> Categories organize navigation.  
> Pages explain them to people.**

## Canonical taxonomy and page policy

The complete BuiltToResist taxonomy should be organized for readers into seven broad browse domains while using the smaller entity ontology underneath.

```text
BuiltToResist
│
├── History
│   ├── Revolutions
│   ├── Uprisings
│   ├── Other Historical Events
│   ├── Countries & Polities
│   ├── Historical Eras
│   └── Timelines
│
├── Movements & Actors
│   ├── Movements
│   ├── Leaders
│   ├── Organizations
│   ├── Institutions
│   └── Scholars
│
├── Ideas & Law
│   ├── Ideologies
│   ├── Political Concepts
│   └── Legal Doctrines
│
├── Dynamics
│   ├── Mechanisms
│   └── Outcomes
│
├── Analysis
│   ├── Cases
│   ├── Scenarios
│   └── Questions
│
├── Evidence
│   ├── Primary Sources
│   ├── Books
│   ├── Archives
│   └── Media
│
└── Research & Data
    ├── Research Reports
    ├── Statistics
    └── Datasets
```

That is a **navigation hierarchy**, not the ontology. A scholar remains a `Person`; “Scholar” is their role. A revolution remains a `HistoricalEvent`; “Revolution” is its event classification. A book remains a `CreativeWork`.

### Complete treatment of the requested taxonomy

| Requested concept | Canonical model | Standalone page policy | Principal relationships |
|---|---|---|---|
| **Revolutions** | `HistoricalEvent` + event type `Revolution` | Named revolutions get pages. “Revolutions” itself is a browse hub and concept page. | `occurredIn`, `involvedMovement`, `hasParticipant`, `usedMechanism`, `resultedIn`, `documentedBy`, `partOf` |
| **Uprisings** | `HistoricalEvent` + event type `Uprising` | Named uprisings get pages. The generic term may have a concept page. | Same event relationships |
| **Movements** | `Movement` | Named, identifiable movements get pages. | `operatesIn`, `alignedWithIdeology`, `ledBy`, `usesMechanism`, `participatedIn`, `hasOutcome`, `opposedBy` |
| **Ideologies** | `Concept` with concept kind `Ideology` | Recognized ideological traditions generally deserve pages; narrow adjectival descriptors may remain tags. | `broader`, `narrower`, `related`, `influenced`, `adoptedBy`, `opposedBy` |
| **Leaders** | `Person` + contextual role `Leader` | Person page, not separate “leader object.” | `leads`, `memberOf`, `participatedIn`, `influenced`, `authored` |
| **Institutions** | `Institution` | Reusable institutional forms and historically important named institutions get pages. | `enablesMechanism`, `constrainsMechanism`, `governedByDoctrine`, `involvedInScenario`, `operatesInPolity` |
| **Mechanisms** | `Concept` kind `Mechanism` | Reusable causal/operational mechanisms deserve pages; individual instances do not. | `enabledByInstitution`, `constrainedByInstitution`, `observedInCase`, `contributesToOutcome` |
| **Outcomes** | `Concept` kind `OutcomeType`; specific outcome recorded as statement | Major reusable outcome concepts get pages; each individual occurrence does not. | `resultOf`, `possibleOutcomeOf`, `associatedWith` |
| **Countries** | `Polity`/`Place`, including historical states | Every polity referenced materially should have a page. | `containsPlace`, `predecessor`, `successor`, `siteOf`, `hasInstitution`, `hasLegalDoctrine` |
| **Historical eras** | `Concept` kind `HistoricalEra` | Established or editorially useful periodizations get pages; arbitrary decades remain facets. | `associatedEra`, `precedes`, `overlaps`, `appliesToRegion` |
| **Legal doctrines** | `Concept` kind `LegalDoctrine` | Distinct doctrines with reusable explanatory value get pages. | `appliesInJurisdiction`, `authorizesInstitution`, `constrainsInstitution`, `interpretedBy`, `derivedFromInstrument` |
| **Political concepts** | `Concept` kind `PoliticalConcept` | Substantive concepts get pages. Minor descriptors remain controlled tags. | `broader`, `narrower`, `related`, `contrastsWith`, `appliesTo` |
| **Books** | `CreativeWork` subtype `Book` | Books cited or analyzed materially get bibliographic pages. | `authoredBy`, `about`, `citedBy`, `influenced`, `publishedBy` |
| **Scholars** | `Person` + role `Scholar` | Scholars materially represented in the research corpus get person pages. | `authored`, `studies`, `associatedWithInstitution`, `citedBy` |
| **Primary sources** | **Relationship role**, usually a `CreativeWork`, media item, or archival object | Individually catalogued primary materials can get source pages; “primary” is contextual, not immutable. | `isPrimarySourceFor`, `createdBy`, `heldBy`, `documents`, `transcribedAs` |
| **Cases** | `Analysis` subtype `Case` | Curated analytical cases get pages. | `caseOf`, `analyzesEvent`, `analyzesMovement`, `analyzesMechanism`, `evaluatesOutcome`, `cites` |
| **Scenarios** | `Analysis` subtype `Scenario` | Only intentionally authored hypothetical/forward-looking scenarios get pages. | `assumes`, `involvesInstitution`, `usesMechanism`, `hasPossibleOutcome` |
| **Questions** | `Analysis` subtype `Question` | Every substantive answerable research question can have one canonical page. | `hasAnswer`, `hasDeepAnalysis`, `about`, `supportedBySource` |
| **Timelines** | `Collection` / ordered editorial view | Curated timelines with explanatory value get pages; routine chronology should be generated from event data. | `includesEvent`, `about`, `orderedByDate` |
| **Archives** | Split between `ArchiveOrganization` and `ArchiveCollection` | Repository and collection pages should be distinct. | `holdsCollection`, `containsSource`, `maintainedBy`, `about` |
| **Research reports** | `CreativeWork` subtype `ResearchReport` | Cited or published reports get pages. | `authoredBy`, `usesDataset`, `analyzes`, `cites`, `publishedBy` |
| **Organizations** | `Organization` | Named bodies relevant to the subject get pages. | `participatedIn`, `memberOf`, `ledBy`, `opposedBy`, `supportedBy`, `operatesIn` |
| **Media** | `CreativeWork`/`MediaObject` | Important articles, films, recordings, photographs, maps or media collections can get pages; individual assets need not all receive prose pages. | `createdBy`, `depicts`, `documents`, `publishedBy`, `isPrimarySourceFor` |
| **Statistics** | `Metric`/`Dataset` + `Observation` rows | Page per indicator/series or dataset; **not one page per datapoint**. | `measures`, `observes`, `forPlace`, `forPeriod`, `derivedFromDataset` |

### Several categories should be roles, not entity classes

This prevents important semantic errors.

**Leader** is a role of a person in relation to a movement, organization, or event:

```text
Person A
  └─ leads → Movement B
       qualifier: 1980–1989
       qualifier: role = chair
```

A person might simultaneously be a scholar, dissident, government official, organizer, author, and movement leader.

**Scholar** is likewise a person role or professional classification.

**Primary source** is especially important to model relationally. A memoir can be a primary source for the author's participation in an event and a secondary interpretation of an earlier period. Calling the work intrinsically “PrimarySource” would erase that distinction. The canonical assertion should therefore be:

```text
CreativeWork BTR-...
   └─ isPrimarySourceFor → HistoricalEvent BTR-...
```

rather than:

```text
CreativeWork type = permanently-primary-source
```

The browse page `/primary-sources/` can simply query all works having one or more `isPrimarySourceFor` statements.

### Revolutions and uprisings should not be separate database universes

Both should inherit from `HistoricalEvent`.

```text
HistoricalEvent
├── Revolution
├── Uprising
├── Rebellion
├── ProtestWave
├── StrikeWave
├── Coup
├── Transition
├── Crackdown
└── OtherEvent
```

An event can carry more than one scholarly classification if necessary:

```text
Event: BTR000042
classification: Uprising
  source: Scholar A
  rank: preferred

classification: Revolution
  source: Scholar B
  status: disputed
```

The point is not to decide every historiographical dispute in the database schema. It is to make the schema capable of preserving the dispute. Qualified statements and source references are the appropriate architecture for that requirement. citeturn14view8turn14view9

### Institutions and organizations must remain distinct

This is one of the most consequential distinctions in the ontology.

An **organization** is an actor or body:

```text
Supreme Court of Country X
Trade Union Y
Army Z
Political Party Q
Ministry of Interior
```

An **institution** is a durable system of rules, offices, practices, authority, or political structure:

```text
constitutional review
bicameral legislature
military conscription
emergency rule
competitive elections
monarchy
federalism
civil service
```

A particular body can be related to an institutional form, but they should not be interchangeable.

For example:

```text
Supreme Court of X       → Organization
constitutional judiciary → Institution
judicial review           → LegalDoctrine / PoliticalConcept
```

This allows queries such as:

> “Find cases involving constitutional courts where judicial review constrained an emergency-powers mechanism.”

That query becomes difficult if “court,” “judiciary,” and “judicial review” are flattened into the same category.

### What deserves a page

An entity should normally receive a standalone encyclopedia page when **at least one of the following is true** and it passes the identity test below:

It is a named historical entity of independent importance; it is likely to be a search destination; it needs explanation beyond a one-line definition; it has meaningful inbound or outbound graph relationships; it is repeatedly cited across the encyclopedia; or it is an editorial object deliberately created by BuiltToResist, such as a case, question, timeline, scenario, or report.

The **identity test** is stricter:

> Can an editor state clearly what this thing is, distinguish it from neighboring things, assign it a stable ID, and cite independent evidence for it?

If yes, it can become an entity.

For reusable concepts, a useful promotion threshold is:

```text
Distinct definition                    required
At least 2 reliable supporting sources required
At least 3 substantive uses/inbound links preferred
Meaning beyond a simple facet          required
Likely to receive explanatory prose    preferred
```

Major concepts can be promoted before reaching the usage threshold; the threshold is a governance heuristic rather than a law.

### What should remain a tag or field

Do **not** create pages merely because a descriptor appears frequently.

Good tag/facet candidates include:

- geographic region;
- century or decade;
- violent/nonviolent classification of a particular episode;
- success status;
- source format;
- language;
- research theme;
- protest tactic subtype;
- institutional sector;
- degree of certainty;
- editorial workflow status.

Some terms begin as controlled tags and later become pages. The underlying ID should survive that promotion.

For example, `elite-defection` might initially be a mechanism tag. When enough cases and literature accumulate, its `Concept` entity can receive an explanatory page without requiring any retagging because the tag assignments already reference the same concept ID.

**Tags should therefore be concept-backed, not strings.**

Instead of:

```text
tags = ["elite defection", "military", "regime change"]
```

store:

```text
tags = [
  BTR-C-00118,
  BTR-C-00043,
  BTR-C-00331
]
```

with each concept carrying a preferred label, alternate labels, definition, concept scheme, and broader/narrower relationships. SKOS explicitly provides preferred, alternate and hidden labels as well as broader, narrower and related concept relationships, making it well suited to this layer. citeturn14view1turn14view2

## Canonical relationships and graph schema

The heart of BuiltToResist should be a **small governed relationship registry**, rather than hundreds of ad hoc predicates invented page by page.

### Canonical relationship families

The MVP should aim for roughly **25–40 well-defined predicates**. New predicates should require schema review.

| Family | Canonical predicates | Examples |
|---|---|---|
| **Classification** | `instanceOf`, `classifiedAs`, `broaderThan`, `narrowerThan`, `relatedConcept` | Revolution → type of HistoricalEvent |
| **Geography** | `occurredIn`, `operatesIn`, `appliesInJurisdiction`, `locatedIn` | Movement → Country |
| **Temporal** | `precededBy`, `followedBy`, `overlapsWith`, `partOfEvent` | Uprising → Revolution |
| **Participation** | `participatedIn`, `ledBy`, `opposedBy`, `supportedBy`, `memberOf` | Movement → Leader |
| **Ideas** | `alignedWithIdeology`, `influencedBy`, `opposesIdeology` | Movement → Ideology |
| **Mechanisms** | `usesMechanism`, `analyzesMechanism`, `enablesMechanism`, `constrainsMechanism` | Case → Mechanism |
| **Institutions** | `targetsInstitution`, `enabledByInstitution`, `governedByDoctrine`, `involvesInstitution` | Mechanism → Institution |
| **Outcomes** | `resultedIn`, `hasOutcome`, `contributesToOutcome`, `hasPossibleOutcome` | Scenario → Outcome |
| **Legal** | `governsInstitution`, `authorizesInstitution`, `constrainsInstitution`, `appliesInJurisdiction` | Doctrine → Institution |
| **Evidence** | `documentedBy`, `isPrimarySourceFor`, `cites`, `supportedBy`, `heldBy` | Event → Primary Source |
| **Works** | `authoredBy`, `publishedBy`, `about`, `usesDataset` | Book → Scholar |
| **Analysis** | `caseOf`, `hasAnswer`, `hasDeepAnalysis`, `assumesCondition` | Question → Answer |
| **Collection** | `contains`, `includesEvent`, `partOfCollection` | Timeline → Event |
| **Data** | `measures`, `observesEntity`, `forPlace`, `derivedFromDataset` | Statistic → Country |
| **Identity/mapping** | `sameAs`, `exactMatch`, `closeMatch` | BTR concept → external vocabulary |

Use `sameAs` sparingly for genuine identity. Conceptual correspondences that are merely very similar should instead use something like SKOS `exactMatch` or `closeMatch`; SKOS deliberately distinguishes several mapping relationships. citeturn14view1

### Canonical versions of the relationships requested

The examples in the brief should be formalized as follows:

| Requested relationship | Canonical predicate | Important qualifiers |
|---|---|---|
| Movement → country | `operatesIn` | `from`, `to`, `primaryArea`, `scope` |
| Movement → ideology | `alignedWithIdeology` | `from`, `to`, `attribution`, `degree`, `disputed` |
| Movement → outcome | `hasOutcome` | `degree`, `directness`, `status`, `source` |
| Case → mechanism | `analyzesMechanism` | `importance`, `finding`, `confidence` |
| Mechanism → institution | `enabledByInstitution`, `constrainedByInstitution`, or `targetsInstitution` | Do not collapse causal direction into `relatedTo` |
| Institution → scenario | inverse of `involvesInstitution` | `role`, `assumption` |
| Scenario → outcome | `hasPossibleOutcome` | `probability` only if methodologically justified; `conditions` |
| Legal doctrine → institution | `governsInstitution`, optionally `authorizesInstitution`/`constrainsInstitution` | `jurisdiction`, `from`, `to`, `instrument` |
| Historical event → primary sources | `documentedBy` | `sourceRole=primary`, `date`, `locator`, `reliabilityNote` |
| Question → answer | `hasAnswer` | revision/version |
| Answer → deep analysis | `hasDeepAnalysis` | revision/version |
| Deep analysis → sources | `cites` / `supportedBy` | locator, claim supported |

### Why directional predicates matter

Avoid a universal `relatedTo` relationship except as a last-resort editorial association.

These are materially different:

```text
Institution → enables → Mechanism
Institution → constrains → Mechanism
Movement    → targets → Institution
Mechanism   → undermines → Institution
```

Flattening all four into `relatedTo` makes the graph visually dense while destroying most of its analytical value.

The rule should be:

> **If editors would want to filter, rank, infer, or phrase the relationship differently, it deserves a semantically specific predicate.**

At the same time, avoid creating predicates like:

```text
temporaryLeaderOf
formerLeaderOf
coLeaderOf
actingLeaderOf
regionalLeaderOf
```

Use:

```text
leads
```

plus qualifiers:

```text
role = acting leader
scope = regional
from = ...
to = ...
```

This is exactly the kind of contextual information that qualified-statement architectures are designed to represent. citeturn14view8turn14view9

### Every significant relationship should be a statement

The core statement record should support:

```text
statement_id
subject_entity_id
predicate_id
object_entity_id | object_literal

assertion_status
rank
confidence

valid_from
valid_to
date_precision
place_id

created_at
updated_at
reviewed_at
```

And zero or more:

```text
qualifiers[]
citations[]
```

Recommended `assertion_status` vocabulary:

```text
asserted
disputed
uncertain
inferred
attributed
hypothetical
deprecated
```

This is particularly important for **scenarios**. No scenario relationship should accidentally enter the graph as a historical assertion.

For example:

```text
Scenario: General strike under emergency rule
  hasPossibleOutcome → negotiated transition
  assertion_status   → hypothetical
```

versus:

```text
HistoricalEvent: Event X
  resultedIn → negotiated transition
  assertion_status → asserted
```

A single status field prevents an enormous class of semantic errors in downstream AI and graph applications.

### Provenance should attach to individual claims

Citations should not exist only as a bibliography at the bottom of a page. The graph should know which source supports which assertion.

Recommended pattern:

```text
STATEMENT S-4831
subject:     Movement A
predicate:   alignedWithIdeology
object:      Ideology B

qualifier:
  from: 1977
  to: 1984

citation:
  source_id: WORK-2041
  locator: pp. 113–117
  note: author's characterization
```

W3C PROV-O exists specifically to represent provenance information, while the broader W3C Data on the Web recommendations emphasize persistent identifiers, metadata, versioning and provenance as foundations of reusable, trustworthy data. citeturn13view15turn14view5turn14view7

BuiltToResist does not need to expose native PROV-O internally on day one, but its schema should be **PROV-compatible** so provenance can later be exported cleanly.

### Graph model

At a logical level:

```text
(:Entity)-[:STATEMENT]->(:Entity)
```

is insufficient because the relationship itself needs data.

The canonical logical representation is:

```text
(Entity)
   │ subject
   ▼
(Statement) ── predicate ──> (Predicate)
   │
   ├── object ─────────────> (Entity or Literal)
   ├── qualifier ──────────> (Qualifier)
   └── citation ───────────> (Source Entity)
```

A relational database can implement that structure perfectly well; a separate graph database is **not required** for the MVP. The same records can later be projected into RDF, a property graph, or a graph-search index.

### Domain and range rules

Every predicate should itself have metadata:

```text
predicate:
  code: alignedWithIdeology
  label: aligned with ideology
  inverse_label: ideology of movement
  domain: [Movement, Organization]
  range: [Ideology]
  symmetric: false
  transitive: false
  source_required: true
  allowed_qualifiers:
    - from
    - to
    - attribution
    - certainty
```

Invalid relationships can then be prevented automatically.

For example:

```text
Movement → alignedWithIdeology → Country
```

would fail validation because `Country` is outside the predicate's range.

### Special handling for outcomes and mechanisms

**Mechanism pages should describe reusable processes**, such as:

```text
elite defection
general-strike disruption
security-force fragmentation
information blockade
repression
sanctions pressure
negotiated pact
parallel institution building
```

A particular military unit defecting on a particular date is not a new “mechanism.” It is an event or evidence that the general mechanism occurred.

Similarly, `Outcome` should usually mean a **reusable outcome class**:

```text
regime collapse
leadership replacement
constitutional reform
policy concession
negotiated transition
movement suppression
civil-war escalation
territorial secession
stalemate
```

An actual instance is represented by the relationship:

```text
Event X → resultedIn → constitutional reform
```

with qualifiers that identify the specific reform.

This prevents the ontology from developing thousands of one-off “outcome concepts.”

## URLs, entity identity, tags, categories, and breadcrumbs

BuiltToResist should separate **persistent machine identity** from **human-readable page location**.

W3C guidance on Web identifiers emphasizes simplicity, stability, and manageability, including avoiding implementation-specific pieces in persistent identifiers. W3C also explicitly distinguishes the URI identifying a thing from the HTML or machine-readable document describing that thing. citeturn14view3turn14view4

### Stable entity IDs

Use opaque, semantically neutral IDs:

```text
BTR00000001
BTR00000002
BTR00000003
```

Do **not** encode the entity's current type, country, date, or title into its immutable ID.

Bad:

```text
REV-FRANCE-1789-0001
MOV-POLAND-1980-0021
```

Those identifiers become wrong when classification changes.

Better:

```text
BTR00018427
```

with properties:

```text
type = HistoricalEvent
event_type = Revolution
preferred_label = French Revolution
```

Recommended identifiers:

```text
Entity:     BTR00018427
Statement:  BTRS00049281
Predicate:  BTRP00000117
Page:       BTRPG00003341
Revision:   BTRR00099120
Dataset:    BTRD00000073
```

The prefixes should distinguish technical object classes only, not historical interpretation.

### Persistent identifier URLs

Give every entity a permanent identifier URI:

```text
/id/BTR00018427
```

That URI should remain valid even when page titles or sections change.

The corresponding human page could be:

```text
/event/french-revolution
```

and machine representation:

```text
/api/v1/entities/BTR00018427
```

The three should explicitly identify one another.

```text
/id/BTR00018427
        │
        ├── describes/links → /event/french-revolution
        └── data            → /api/v1/entities/BTR00018427
```

Persistent URIs are also recommended by W3C for datasets and identifiers inside datasets because they enable durable citation, linking, and reuse; the guidance also encourages linking to existing external identifiers when appropriate. citeturn14view5turn14view6

### Human URL structure

Do **not** replicate all 24 taxonomy types as independent technical silos. Use a smaller family structure.

Recommended canonical page paths:

```text
/event/french-revolution
/event/hungarian-uprising-1956

/movement/solidarity-poland
/movement/people-power-movement

/person/vaclav-havel
/person/hannah-arendt

/organization/solidarity-trade-union
/organization/congress-of-south-african-trade-unions

/institution/constitutional-court
/institution/emergency-rule

/concept/nonviolent-resistance
/concept/elite-defection
/concept/liberalism
/concept/martial-law
/concept/regime-change

/place/poland
/place/czechoslovakia

/work/from-dictatorship-to-democracy
/work/declaration-of-the-rights-of-man

/case/...
/scenario/...
/question/...
/timeline/...
/archive/...
/dataset/...
```

The **browse hubs** retain the terminology readers expect:

```text
/revolutions/
/uprisings/
/movements/
/ideologies/
/leaders/
/institutions/
/mechanisms/
/outcomes/
/countries/
/eras/
/legal-doctrines/
/political-concepts/
/books/
/scholars/
/primary-sources/
/cases/
/scenarios/
/questions/
/timelines/
/archives/
/research-reports/
/organizations/
/media/
/statistics/
```

A browse hub is a query/view, not necessarily a fundamental type.

Thus:

```text
/leaders/
```

lists `Person` entities that possess leader relationships.

```text
/scholars/
```

lists `Person` entities with scholar roles.

```text
/primary-sources/
```

lists works and media that have `isPrimarySourceFor` relationships.

That is a major anti-explosion mechanism.

### Slug policy

Human-facing slugs should be:

- lowercase;
- ASCII normalized where necessary;
- hyphenated;
- concise;
- disambiguated only when required.

Examples:

```text
/person/jose-marti
/event/russian-revolution
/event/russian-revolution-1905
/movement/solidarity-poland
```

The slug is **not the identity**. A renamed page simply receives a 301 redirect from its old route while retaining the same `BTR...` ID.

### External identifiers

Each entity may carry many external identifiers:

```text
Wikidata QID
VIAF
ISNI
Library of Congress identifier
ISBN
DOI
ORCID
GeoNames
national archive identifier
university repository identifier
dataset DOI
```

Store these in a repeatable external-ID table:

```text
entity_id
identifier_scheme
identifier_value
identifier_uri
match_type
```

Recommended `match_type`:

```text
same
exactConceptMatch
closeConceptMatch
related
```

This reflects the distinction between actual entity identity and vocabulary mapping. SKOS provides mappings such as `exactMatch`, `closeMatch`, `broadMatch`, `narrowMatch`, and `relatedMatch`, specifically enabling concept systems to interoperate without pretending every correspondence is strict identity. citeturn14view1

### Category strategy

Only a small number of **editorially governed navigation categories** should exist.

Recommended major category schemes:

```text
Content family
Event type
Geography
Historical period
Ideological family
Mechanism family
Institutional domain
Outcome family
Source/evidence type
Research theme
```

Categories must **not** proliferate into every possible combination.

Never manually create categories like:

```text
20th-century-left-wing-revolutions-in-latin-america
successful-nonviolent-anti-authoritarian-movements
military-defection-cases-in-eastern-europe
```

Those are faceted queries:

```text
/event/?era=20th-century
       &region=latin-america
       &ideology=left
```

or curated editorial landing pages if they genuinely deserve narrative treatment.

This single rule prevents thousands of near-empty intersection categories.

### Controlled tag schemes

Public tags should belong to an explicit `ConceptScheme`.

Example:

```text
TagScheme: mechanism
  ├── elite behavior
  │   ├── elite split
  │   └── elite defection
  ├── coercion
  │   ├── repression
  │   └── selective repression
  └── collective action
      ├── mass mobilization
      └── strike disruption
```

Separate scheme:

```text
TagScheme: outcome
  ├── regime transformation
  │   ├── leadership replacement
  │   └── regime collapse
  ├── policy change
  └── repression
```

SKOS `ConceptScheme`, `Concept`, `broader`, `narrower`, `related`, `prefLabel`, `altLabel`, and `scopeNote` directly support this architecture. citeturn14view0turn14view1

Every controlled term should carry:

```text
concept_id
preferred_label
alternate_labels[]
definition
scope_note
scheme
broader_concept[]
related_concept[]
status
created_at
reviewed_at
```

The `scope_note` is crucial. For example:

```text
Preferred label: elite defection

Scope note:
Use when members of an incumbent political, military, economic,
or bureaucratic elite withdraw material support from the ruling
coalition. Do not use merely for public criticism by an individual.
```

That makes the taxonomy usable consistently by editors and agents.

### Historical eras should be associative, not rigid containers

Historical periods vary across region and scholarly tradition.

Therefore:

```text
Event → associatedEra → Cold War
```

is safer than assuming an event is ontologically “inside” exactly one era.

An era can have approximate dates and geographical scope:

```text
HistoricalEra
  start_date
  end_date
  date_precision
  applicable_region[]
  broader_era[]
  scope_note
```

The same event may belong legitimately to multiple overlapping periodizations.

### Countries need historical-polity modeling

Do not maintain only a list of present-day countries.

Use:

```text
Place
 └── Polity
      ├── CurrentCountry
      ├── HistoricalState
      ├── Colony
      ├── Territory
      └── DisputedPolity
```

with:

```text
predecessor
successor
valid_from
valid_to
capital
territory
```

Then an event can be located in the **polity that existed at the time**, while geographical relationships can also point toward present-day locations where useful.

### Breadcrumb hierarchy

Breadcrumbs should represent **one comprehensible editorial path**, not every possible graph relationship. Google describes a breadcrumb as the page's position in the site's hierarchy, allowing users to move upward through that hierarchy. citeturn13view10

Recommended examples:

```text
Home
› History
› Revolutions
› French Revolution
```

```text
Home
› Ideas & Law
› Ideologies
› Anarchism
```

```text
Home
› Dynamics
› Mechanisms
› Elite Defection
```

```text
Home
› Evidence
› Books
› From Dictatorship to Democracy
```

```text
Home
› Analysis
› Questions
› Why do security forces defect?
```

Keep canonical breadcrumbs shallow: normally **Home + two or three meaningful levels**.

Do not make geography part of the breadcrumb for every page:

```text
Home > Europe > Eastern Europe > Poland > 1980s > Movements > Solidarity
```

That is a taxonomy trap. Poland, Eastern Europe, and the 1980s should be facets and contextual links.

Every page can nevertheless display a **context panel**:

```text
Country: Poland
Era: Cold War
Ideology: multiple
Mechanisms: strikes, elite negotiation
Outcome: negotiated transition
```

Breadcrumbs answer **“where am I in the site?”**

Facets answer **“what is this connected to?”**

## Human navigation and related-page logic

A graph-driven encyclopedia should still feel simple to a human visitor. The graph belongs underneath the interface; the primary navigation should expose recognizable research journeys.

### Global navigation

A scalable primary navigation could be:

```text
Explore
History
Movements & Actors
Ideas & Law
Dynamics
Evidence
Analysis
Research & Data
Search
```

**Explore** should be the principal discovery interface rather than an enormous category tree.

Its facets should include:

```text
Entity type
Country / polity
Region
Date range
Historical era
Event type
Movement
Ideology
Mechanism
Institution
Outcome
Legal doctrine
Source type
Research theme
```

Users should be able to construct questions such as:

> Revolutions in Europe, 1900–2000, involving military defections and resulting in regime change.

or:

> Cases involving labor movements, general strikes, and negotiated transitions.

The database can answer those from graph dimensions without any bespoke “category page.”

### Entity-page template

Most substantive entity pages should share a stable anatomy:

```text
Title
Aliases / alternate names
One-sentence definition

Lead summary
Key facts / entity card

Main narrative
  Background
  Development / operation
  Significance
  Interpretations / disputes

Structured relationships
  People
  Movements
  Events
  Countries
  Ideas
  Institutions
  Mechanisms
  Outcomes

Chronology, when relevant

Primary sources
Scholarship / further reading

Data / statistics, when relevant

Related pages
Revision and citation information
```

Not every type needs every block. Type-specific modules should extend this shell.

A **mechanism page**, for example, should emphasize:

```text
Definition
Scope / exclusions
Causal logic
Conditions
Observed cases
Institutions that enable/constrain it
Possible outcomes
Competing interpretations
Evidence
```

A **country page** should emphasize:

```text
Historical polities
Revolutions and uprisings
Movements
Institutions
Legal doctrines
Organizations
Timeline
Research
Data
```

A **book page** should emphasize:

```text
Bibliographic record
Author
Summary
Principal arguments
Subjects
Events/cases discussed
Reception/importance
Citations elsewhere on BuiltToResist
```

### Questions as a distinct editorial architecture

Questions are extremely useful for both readers and agents because they map natural-language research intent onto encyclopedia evidence.

A canonical question page should be:

```text
Question
   ↓
Direct Answer
   ↓
Deep Analysis
   ↓
Counterarguments / uncertainty
   ↓
Examples / cases
   ↓
Sources
   ↓
Related Questions
```

In the knowledge graph:

```text
Question
  ├─ hasAnswer ────────> AnswerBlock
  ├─ about ────────────> Concept / Event / Mechanism
  └─ relatedQuestion ──> Question

AnswerBlock
  └─ hasDeepAnalysis ──> AnalysisBlock

AnalysisBlock
  ├─ cites ────────────> Source
  ├─ usesCase ─────────> Case
  └─ discusses ────────> Entity
```

`AnswerBlock` and `AnalysisBlock` do **not** initially need their own public pages. Give them stable fragment or content IDs:

```text
/question/why-do-security-forces-defect#answer
/question/why-do-security-forces-defect#analysis
```

and internal IDs:

```text
BTRBLOCK000492
BTRBLOCK000493
```

Promote a block to a full page only if it becomes independently reusable or citable.

This preserves the requested:

```text
Question → answer → deep analysis → sources
```

without creating thousands of tiny pages.

### Timelines should usually be generated views

A historical event should have a date in the event graph once.

A timeline then selects and orders those events:

```text
Timeline
  ├─ includesEvent → Event A [order 1]
  ├─ includesEvent → Event B [order 2]
  └─ includesEvent → Event C [order 3]
```

Do not copy event descriptions into separate timeline records.

Only curated timelines with explanatory framing deserve independent pages:

```text
/timeline/fall-of-communism-eastern-europe
```

A purely automatic “events in 1987” view can remain a generated query page.

### Archive architecture

“Archive” is ambiguous and should be split.

```text
Archive Organization
National Archives of ...
Library of ...
University collection repository
```

versus:

```text
Archive Collection
Papers of Person X
Movement Y Collection
Underground Press Collection
```

The graph becomes:

```text
ArchiveOrganization
  └─ holdsCollection → ArchiveCollection
                         └─ containsSource → PrimarySource
```

This matters because repository, collection, and document have different metadata and different relationships.

For digitized archival objects, IIIF is a particularly valuable interoperability layer. IIIF Presentation defines Collections containing Collections or Manifests, with Manifests organizing canvases and annotations, which maps naturally to digital collections, scanned documents, pages, images, transcripts, and annotations. citeturn14view12

### Statistics architecture

Do not model:

```text
“Population of Country X in 1974 = 12,391,000”
```

as a standalone encyclopedia page.

Use:

```text
Metric
  ↓
Dataset / Series
  ↓
Observation
```

Example:

```text
Metric: protest fatalities
Dataset: BTR Political Mobilization Indicators
Observation:
  subject = Event X
  place = Country Y
  period = 1989
  value = 31
  unit = persons
  method = ...
  source = ...
  uncertainty = ...
```

Standalone pages belong to:

- the metric definition;
- the dataset;
- a meaningful statistical series or visualization;
- occasionally a major research finding.

Datapoints live in the data layer.

DCAT is designed for cataloguing datasets and data services and supports identifiers, titles, creators, publishers, themes, keywords, landing pages, versions and related metadata. It explicitly supports SKOS concept schemes for thematic dataset classification, making a DCAT-compatible catalog a strong fit for BuiltToResist's statistical layer. citeturn14view10turn14view11

### Related-page rules

“Related pages” should not be hand-maintained except for editorial overrides.

Use a reproducible ranking algorithm.

A good starting score:

| Signal | Suggested weight |
|---|---:|
| Explicit direct graph relationship | +40 |
| Same mechanism | +25 |
| Same event or movement | +25 |
| Same specific institution | +20 |
| Same outcome | +18 |
| Same country/polity | +15 |
| Same ideology | +12 |
| Temporally overlapping | +10 |
| Shared sources/citations | +8 |
| Same broad era | +5 |
| Editorial featured relationship | +20 |

Then apply diversity constraints:

```text
maximum related results: 10
maximum same-type results: 4
prefer at least 3 different entity families
deduplicate aliases / redirects
exclude current page
exclude deprecated entities
downrank weak matches based only on broad era or country
```

Most importantly, show the reason:

```text
Elite Defection
Related because:
Mechanism observed in this case
```

```text
Polish Round Table Talks
Related because:
Same movement · same country · direct event relationship
```

This makes the graph legible rather than mysterious.

### Context-specific relations should outrank similarity

A reader on a movement page is generally better served by:

```text
Leaders
Events
Ideologies
Mechanisms
Outcomes
Primary Sources
```

than by ten other pages that happen to share the tag “Cold War.”

Therefore related-page ranking should prioritize **typed direct relationships**, then increasingly broad similarity signals.

## Structured data and agent-readable architecture

BuiltToResist should treat machine-readable access as a first-class publication format, not an afterthought.

The Web standards picture is complementary:

- **Schema.org / JSON-LD** for page-level semantic markup;
- **SKOS** for controlled vocabularies;
- **PROV-O-compatible provenance** for claim origin and derivation;
- **DCAT** for datasets and services;
- **IIIF** for digital primary-source collections and media;
- persistent HTTP identifiers for entities;
- an API and bulk downloads for complete access.

W3C's Data on the Web guidance explicitly recommends persistent identifiers, machine-readable standardized formats, multiple representations, APIs, metadata, vocabulary reuse, subsets and bulk downloads. citeturn14view5turn14view6turn14view7

### JSON-LD on every canonical page

Every canonical page should emit JSON-LD whose `@id` is the permanent **entity identifier**, not merely the current page URL.

Conceptual example:

```json
{
  "@context": [
    "https://schema.org",
    {
      "btr": "https://builtoresist.org/vocab/"
    }
  ],
  "@id": "https://builtoresist.org/id/BTR00018427",
  "@type": ["Event", "btr:Revolution"],
  "name": "Example Revolution",
  "url": "https://builtoresist.org/event/example-revolution",
  "startDate": "1989-01-01",
  "endDate": "1989-12-31"
}
```

Schema.org provides generic types for entities including CreativeWork, Book, audio/image/video objects, Event, Organization, Person, and Place. citeturn13view13

Google recommends JSON-LD for structured data, recommends using the most specific applicable type, and requires structured data to represent the actual visible content rather than hidden or unrelated information. Structured-data implementation should therefore mirror the page rather than becoming a second hidden encyclopedia. citeturn13view11

### Recommended structured-data mapping

| BuiltToResist entity | Public structured-data mapping |
|---|---|
| Historical Event / Revolution / Uprising | `schema:Event` + internal `btr:` type |
| Person / Leader / Scholar | `schema:Person` |
| Organization | `schema:Organization` |
| Institution as abstract system | `schema:Thing` or `DefinedTerm` + internal type |
| Country / Place | `schema:Place` / applicable geographic subtype |
| Ideology | `schema:DefinedTerm` + `btr:Ideology` |
| Mechanism | `schema:DefinedTerm` + `btr:Mechanism` |
| Outcome type | `schema:DefinedTerm` + `btr:Outcome` |
| Political concept | `schema:DefinedTerm` |
| Legal doctrine | `schema:DefinedTerm` + internal legal type |
| Historical era | `schema:DefinedTerm` |
| Book | `schema:Book` |
| Research report | `schema:CreativeWork` / more specific work type where applicable |
| Primary-source document | appropriate `CreativeWork` subtype |
| Image | `schema:ImageObject` |
| Video | `schema:VideoObject` |
| Audio | `schema:AudioObject` |
| Dataset / statistical series | `schema:Dataset` + `dcat:Dataset` |
| Timeline | `schema:CollectionPage` / ordered `ItemList` |
| Question | `schema:Question` within its Web page representation |
| Answer | `schema:Answer` |
| Breadcrumbs | `schema:BreadcrumbList` |
| Browse/index page | `schema:CollectionPage` / `ItemList` |

Do not equate “can be expressed in schema.org” with “will receive a search-engine rich result.” Google explicitly states that correct structured markup does not guarantee a rich result. citeturn13view11

For dataset pages specifically, dataset structured metadata can expose elements such as the name, description, creator, and distribution formats, and Google documents this metadata as a mechanism for dataset discovery. citeturn13view12

### Internal vocabulary namespace

Publish BuiltToResist's own controlled semantics:

```text
/vocab/
/vocab/Movement
/vocab/Revolution
/vocab/Mechanism
/vocab/hasOutcome
/vocab/analyzesMechanism
```

and a machine-readable vocabulary document:

```text
/vocab/btr.ttl
/vocab/btr.jsonld
```

This vocabulary should define:

```text
Classes
Predicates
Domain/range rules
Inverse predicates
Concept schemes
Status vocabularies
Version
Change log
```

The site can then use established standards where they fit and `btr:` extensions where they do not.

### Agent-readable indexes

A modern encyclopedia should expose **deterministic machine inventories**, rather than expecting agents to discover everything through HTML crawling.

Recommended endpoints:

```text
/sitemap.xml

/api/v1/entities
/api/v1/entities/{id}
/api/v1/statements
/api/v1/search
/api/v1/concepts
/api/v1/changes

/data/catalog.jsonld
/data/entities.ndjson
/data/statements.ndjson
/data/concepts.jsonld
/data/datasets/

/index/events.json
/index/movements.json
/index/people.json
/index/concepts.json
/index/works.json
/index/sources.json
/index/datasets.json

/vocab/btr.jsonld
/vocab/btr.ttl
```

Each machine index should minimally expose:

```text
id
type
preferred_label
canonical_url
updated_at
language
status
```

and permit pagination or cursor traversal.

### Sitemap strategy

Use a sitemap index split by stable content families:

```text
/sitemaps/events.xml
/sitemaps/movements.xml
/sitemaps/people.xml
/sitemaps/concepts.xml
/sitemaps/places.xml
/sitemaps/works.xml
/sitemaps/analysis.xml
/sitemaps/datasets.xml
```

Sitemaps are explicitly intended to tell search engines which site URLs are available for crawling and can include update metadata. citeturn13view14

The sitemap should list **canonical public pages**, not every faceted query permutation.

### Bulk data export

Publish versioned dumps:

```text
/data/releases/2026-09/entities.ndjson
/data/releases/2026-09/statements.ndjson
/data/releases/2026-09/concepts.jsonld
/data/releases/2026-09/citations.ndjson
```

plus:

```text
manifest.json
checksums
license
schema_version
created_at
previous_release
```

This gives research tools and agents a much more reliable interface than scraping HTML. It also aligns with W3C recommendations for bulk downloads, machine-readable formats, metadata, APIs, and version-aware data publication. citeturn14view7

### Content negotiation

The permanent resource ID can optionally support content negotiation:

```text
GET /id/BTR00018427
Accept: text/html
→ human representation

Accept: application/ld+json
→ linked-data representation
```

Alternatively, the identifier can redirect to explicitly linked HTML and JSON-LD resources. W3C documents both the distinction between entity identifiers and description-document identifiers and the value of explicitly linking the relevant URIs. citeturn14view4

### A DCAT catalog for research assets

Publish:

```text
/data/catalog.jsonld
```

containing catalog records for:

```text
datasets
statistical series
bulk knowledge-graph dumps
research corpora
API data services
```

DCAT's `Catalog`, `Dataset`, `DataService`, `Distribution`, and `CatalogRecord` structures are directly suited to this requirement, including versions, landing pages, identifiers, publishers, licenses, themes, and related metadata. citeturn14view10turn14view11

### IIIF for archives and scanned sources

Where BuiltToResist hosts or interoperates with digitized archival material:

```text
Archive Collection
      ↓
IIIF Collection
      ↓
Manifest
      ↓
Canvas
      ↓
page/image/audio/video
      ↓
annotations/transcription
```

IIIF Presentation explicitly models Collections, Manifests, Canvases, annotation pages, and ordered ranges, making it well suited to books, manuscripts, posters, newspapers, photographs and archival sequences. citeturn14view12

### Optional agent convenience file

An `llms.txt`-style file can be supplied as a **convenience index**, but it should never be the authoritative representation of the encyclopedia.

For example:

```text
/llms.txt
```

might point agents toward:

```text
API documentation
entity index
knowledge-graph vocabulary
bulk dumps
citation policy
content license
```

No fact should exist *only* in this file. The durable machine contract should remain HTTP pages, JSON-LD, stable identifiers, sitemaps, APIs, and downloadable data.

## Minimum viable schema and governance for 10,000+ pages

The minimum viable implementation should be **entity-centric, relationship-centric, and provenance-aware from day one**. Trying to retrofit identifiers and structured relationships after thousands of articles exist is considerably harder than allowing prose complexity to grow on top of a stable data core.

### Core database schema

A practical MVP requires roughly the following tables.

| Table | Purpose | Essential fields |
|---|---|---|
| `entities` | One record per identified thing or concept | `id`, `entity_type`, `preferred_label`, `summary`, `status`, temporal fields |
| `entity_types` | Governed class registry | `code`, `parent_type`, external mappings |
| `labels` | Preferred/alternate/historical names | `entity_id`, `language`, `label`, `label_type` |
| `pages` | Published human pages | `page_id`, `entity_id`, `canonical_path`, `title`, `body`, `status`, revision |
| `predicates` | Canonical relationship vocabulary | `predicate_id`, `code`, domain, range, inverse, qualifier rules |
| `statements` | Entity-to-entity or entity-to-literal assertions | subject, predicate, object, status, rank, dates |
| `statement_qualifiers` | Context of assertions | statement, qualifier predicate, value |
| `statement_sources` | Claim-level provenance | statement, source entity, locator, note |
| `external_ids` | Links to outside authority identifiers | entity, scheme, value, match type |
| `concept_schemes` | Controlled facet vocabularies | scheme ID, title, description |
| `concept_assignments` | Tags/classification | subject, concept, relation/relevance |
| `media_assets` | Hosted or referenced media | entity, media type, URI, rights, creator, alt text |
| `observations` | Statistical datapoints | metric, subject/place, period, value, unit, dataset |
| `revisions` | Editorial history | object, revision, author/editor, timestamp, change note |

This is enough to build the site without locking the project into a page-centric CMS model.

### Canonical entity record

A generic entity should minimally support:

```text
id
entity_type
preferred_label
short_label
description
status

start_date
end_date
date_precision

created_at
updated_at
reviewed_at
```

Do not add 100 nullable columns to `entities`.

Type-specific fields can live in extensions:

```text
event_details
person_details
work_details
place_details
dataset_details
```

or equivalent normalized tables.

### Minimum event record

```text
entity_id
event_type
start_date
end_date
date_precision

locations[]
participants[]
parent_event[]
classifications[]
```

Location and participants should be relationships, not comma-separated fields.

### Minimum concept record

Concepts should still use the common entity table, with additional taxonomy data:

```text
entity_id
concept_kind
concept_scheme
preferred_label
alternate_labels[]
scope_note
broader[]
narrower[]
related[]
```

The SKOS distinction between concepts, concept schemes, collections, labels, semantic relations and mapping relations provides a mature model for precisely this purpose. citeturn14view0turn14view1turn14view2

### Minimum work/source record

```text
entity_id
work_type
title
creator[]
publication_date
publisher
language
identifier[]
rights
digital_copy[]
archive_collection[]
```

“Primary source” should then remain a relationship:

```text
work_id
  isPrimarySourceFor
event_id
```

### Minimum dataset/statistics record

```text
Dataset
  id
  title
  creator
  publisher
  description
  temporal_coverage
  geographic_coverage
  methodology
  license
  version
  distributions[]
```

and separate:

```text
Observation
  metric_id
  subject_id
  place_id
  period_start
  period_end
  value
  unit
  uncertainty
  method
  dataset_id
```

DCAT already defines a catalog model with datasets, distributions, catalog records, data services and metadata such as identifiers, titles, creators, publishers, versions and landing pages; BuiltToResist should map its public data catalog to that model instead of inventing an incompatible dataset standard. citeturn14view10turn14view11

### Relationship validation rules

Every statement should pass several automatic checks:

| Check | Example |
|---|---|
| Domain validity | `alignedWithIdeology` subject must be a permitted actor type |
| Range validity | object must be `Ideology` |
| Temporal logic | `valid_from` cannot follow `valid_to` |
| Hypothetical isolation | scenario claims must carry hypothetical status |
| Source requirement | contested factual claims require citations |
| Duplicate detection | equivalent statement already exists |
| Self-relation check | disallow nonsensical self-links unless predicate permits |
| Deprecated target check | redirect statements toward canonical merged entity |
| Qualifier validity | only allowed qualifiers for predicate |
| Identity rule | `sameAs` requires stronger evidence than `closeMatch` |

### The anti-taxonomy-explosion rules

These should be written into the editorial handbook as hard governance principles.

**Use one canonical entity, not duplicate entities by context.**

Do not create:

```text
Elite Defection in Revolutions
Elite Defection in Uprisings
Elite Defection in Nonviolent Campaigns
```

Create:

```text
Elite Defection
```

and relationships to relevant cases.

**Never create compound tags when facets can be combined.**

Bad:

```text
1980s-Polish-nonviolent-labor-movements
```

Good:

```text
decade = 1980s
country = Poland
tactic = nonviolent action
actor base = labor
entity type = Movement
```

**Keep the public browse tree shallow.**

The graph can be highly connected and SKOS concepts can have broader/narrower relationships, but the visible site hierarchy should rarely exceed roughly three meaningful levels beneath Home.

**Prefer qualifiers over new predicates.**

Do not create `formerLeaderOf`, `interimLeaderOf`, `coLeaderOf` if `leads` plus role/time qualifiers represents the distinction.

**Prefer roles over duplicate entity types.**

Leader and Scholar → `Person`.

Primary Source → relationship role of a work/media item.

Current Country and Historical State → subtypes/roles of `Polity`.

**Prefer generated collections over permanent category pages.**

“Revolutions in Africa in the 1970s” is a query unless editors intentionally write a substantive synthesis around that grouping.

**Use controlled tags only.**

Public editors should not be able to introduce arbitrary new tags merely by typing a string. They should select an existing concept, propose an alias, or submit a new concept for taxonomy review.

**Separate synonyms from concepts.**

These:

```text
labor movement
labour movement
```

are normally labels for one concept, not two tags.

SKOS's preferred and alternate-label model is specifically suited to that requirement. citeturn14view1

**Merge first; specialize only with evidence.**

Before creating a new mechanism or outcome concept, editors should answer:

```text
Is this truly different from an existing concept?
Can the difference be stated in one sentence?
Will users need to query them separately?
Is there literature distinguishing them?
```

If not, use an existing concept plus qualifiers.

### Promotion lifecycle for tags

A concept can progress without changing identity:

```text
Controlled Tag
      ↓
Concept with definition
      ↓
Concept with relationships
      ↓
Full encyclopedia page
```

Example:

```text
BTR00083211 = selective repression
```

At first:

```text
no page
used as controlled mechanism tag
```

Later:

```text
/concept/selective-repression
```

The same `BTR00083211` remains underneath both states.

This architecture makes “does this deserve a page?” an editorial decision rather than a database migration.

### Entity lifecycle

Use explicit states:

```text
draft
review
published
disputed
deprecated
merged
redirected
```

Never delete an identifier merely because two entities were later judged to be identical.

Instead:

```text
BTR000381 → mergedInto → BTR000227
```

and preserve the old identifier as a redirect/tombstone record.

Stable identifiers are a core recommendation of Web data best practice because persistence enables references and citations to remain meaningful over time. citeturn14view3turn14view5

### Minimum relationship set to launch

The site does not need hundreds of predicates before publication. The following set is enough to support nearly all relationships in the brief:

```text
instanceOf
classifiedAs
broaderThan
narrowerThan
relatedConcept

occurredIn
operatesIn
partOfEvent
precededBy
followedBy

participatedIn
ledBy
memberOf
supportedBy
opposedBy

alignedWithIdeology

usesMechanism
analyzesMechanism
enabledByInstitution
constrainedByInstitution
targetsInstitution

hasOutcome
resultedIn
hasPossibleOutcome

governsInstitution
appliesInJurisdiction

documentedBy
isPrimarySourceFor
cites
heldBy

authoredBy
publishedBy
about

caseOf
involvesInstitution
hasAnswer
hasDeepAnalysis

contains
includesEvent

measures
derivedFromDataset
```

Several can later be specialized when actual editorial use proves the need.

### Minimum controlled concept schemes

Start with these schemes:

```text
event-type
ideology
mechanism
outcome
institutional-domain
legal-domain
historical-era
geographic-region
tactic
source-type
research-theme
assertion-status
```

Do **not** create a separate “tag system” for every page family.

### Minimum page templates

At launch, BuiltToResist can cover the requested universe with roughly ten reusable templates:

```text
Event
Movement
Person
Organization/Institution
Concept
Place/Polity
Work/Source
Analysis
Collection/Timeline/Archive
Dataset/Statistics
```

The 24 requested public categories become views or subtypes of those templates.

This is substantially more scalable than maintaining 24 independently designed content architectures.

### The canonical model in compact form

The entire architecture can ultimately be summarized as:

```text
ENTITY
│
├── HistoricalEvent
│   ├── Revolution
│   └── Uprising
│
├── Movement
├── Person
├── Organization
├── Institution
├── Place / Polity
│
├── Concept
│   ├── Ideology
│   ├── Mechanism
│   ├── Outcome
│   ├── LegalDoctrine
│   ├── PoliticalConcept
│   └── HistoricalEra
│
├── CreativeWork
│   ├── Book
│   ├── ResearchReport
│   ├── SourceDocument
│   └── Media
│
├── Analysis
│   ├── Case
│   ├── Scenario
│   └── Question
│
├── Collection
│   ├── Timeline
│   └── ArchiveCollection
│
└── Dataset
    ├── MetricSeries
    └── Observation
```

Every entity then connects through:

```text
ENTITY
   ↓
STATEMENT
   ├── predicate
   ├── object
   ├── qualifiers
   └── citations
```

while every published article connects through:

```text
PAGE
  ├── describes → ENTITY
  ├── sections
  ├── citations
  ├── breadcrumb
  ├── JSON-LD
  └── related-page query
```

and discovery is generated through:

```text
CONCEPT SCHEMES
  +
RELATIONSHIPS
  +
FACETS
  +
SEARCH
```

rather than thousands of manually maintained cross-categories.

### Recommended minimum viable BuiltToResist specification

For a site intended to grow beyond 10,000 pages, the minimum viable architecture should therefore commit from the beginning to the following contracts:

| Layer | Minimum contract |
|---|---|
| **Identity** | Every real entity receives one immutable opaque `BTR` identifier |
| **Publishing** | Every entity with substantive editorial treatment has one canonical page |
| **Ontology** | A small set of core entity families, not one database type for every browse category |
| **Taxonomy** | SKOS-compatible controlled concept schemes with aliases, scope notes, broader/narrower relations |
| **Graph** | Governed subject-predicate-object statements |
| **Context** | Relationship qualifiers for time, place, role, uncertainty, hypothetical status |
| **Evidence** | Claim-level citations and provenance |
| **URLs** | Stable `/id/` identity separated from mutable human-readable slugs |
| **Navigation** | Shallow type-oriented breadcrumbs plus multidimensional facets |
| **Related pages** | Graph-driven, typed, explainable recommendations |
| **Historical modeling** | Revolutions and uprisings are event classifications under a shared event model |
| **People** | Leader and Scholar are person roles rather than duplicate person types |
| **Sources** | Primary-source status is contextual and relational |
| **Institutions** | Institutions are distinct from organizations |
| **Statistics** | Dataset/metric pages plus observations; no page-per-datapoint architecture |
| **Analysis** | First-class Cases, Scenarios and Questions with explicit hypothetical/analytic semantics |
| **Structured data** | JSON-LD on canonical pages; schema.org plus BuiltToResist vocabulary |
| **Data interoperability** | SKOS for vocabularies, DCAT for datasets, PROV-compatible provenance, IIIF where archival media warrant it |
| **Agents** | API, type indexes, JSON-LD, sitemaps, bulk NDJSON/data releases and machine-readable vocabulary |
| **Governance** | No uncontrolled public tags; no compound categories; predicate registry; entity merge/redirect rules |
| **Evolution** | A concept can move from tag → structured concept → full page without changing its ID |

The governing idea is simple: **BuiltToResist should not be built as 10,000 articles with tags attached. It should be built as a durable graph of identifiable historical entities, concepts, claims and evidence, with 10,000+ human-readable pages rendered on top of it.** SKOS supplies the vocabulary architecture; qualified statements supply the historical nuance; persistent identifiers make the knowledge durable; provenance makes claims auditable; DCAT and IIIF make research assets interoperable; and structured JSON-LD/API surfaces make the same encyclopedia intelligible to search engines, researchers, and AI agents. Those choices align closely with established Web recommendations for linked knowledge organization, persistent identifiers, machine-readable publication, APIs, standardized vocabularies, provenance, and reusable data. citeturn14view0turn14view8turn14view5turn14view7turn14view10turn14view12