Last comprehensive update: 2026-07-10. This file was consolidated on
that date after several sessions of incremental edits — if you're reading
this much later, treat everything below as accurate as of that date, then
check git log --oneline -20 for what's happened since. Don't trust this
file's currency blindly; the git history is the real source of truth.
An RDF knowledge graph connecting Nate Hagens' "Great Simplification"
(TGS) framework — energy, ecology, economics, human behavior — to the
philosophers, scientists, and activists whose ideas it echoes, draws on,
or usefully contrasts with. Goal: make the framework navigable and
legible to students, activists, data people, and general audiences.
Repo: https://github.com/MJSullivan56/nate-hagens-kg (branch main).
- RDF/Turtle as source of truth, all in
data/seed/(the separateontology/folder was dissolved 2026-07-11 — see governance note below) - Oxigraph as the query engine (installed via
brew tap oxigraph/oxigraph && brew install oxigraph; confirmed working, version 0.5.0-beta.4 as of last check) - DuckDB as a staging/review database for LLM-proposed candidate links
(
extraction/staging.duckdb, gitignored, not committed) - Python venv (
make venv) for all scripting — see KNOWN GOTCHA below before assuming a freshmake venvwill just work - VS Code: SPARQL Notebook extension (
Zazuko.sparql-notebook) lets you query.ttlfiles directly in the editor — install status on MJSullivan's machine was never confirmed as of last check, verify before assuming
The project's scope expanded beyond Hagens alone — MJSullivan intends to
apply this same methodology to other thinkers, with Heather Cox Richardson
named as the concrete next candidate. This forced a real architectural
change, not just a naming tweak: tgs: literally stands for "The Great
Simplification," so it can never correctly hold another thinker's data.
The split: every CLASS, PROPERTY, and controlled-vocabulary INDIVIDUAL
(the ConfidenceType/ReliabilityType/PolarityType enum members —
every domain will reuse the exact same Curated/Candidate/Corroborated/
Disputed states) now lives under thinkr: (http://example.org/thinker#)
— this is the reusable ontology. Every actual DOMAIN INDIVIDUAL (every
Person, Concept, School, LinkNote, Evidence) stays under tgs: —
this is Hagens-specific data. data/seed/tgs-core.ttl (formerly
ontology/schema.ttl, see the 2026-07-11 governance reorganization note
below) is 100% thinkr:,
zero tgs: — verified by grep, not just assumed.
A real bug worth knowing about if you extend this pattern again: the
first rename pass used a regex matching bare class names like Person,
which incorrectly also matched the START of tgs:Person.MarcusAurelius
(since nothing excluded a following .), silently reclassifying every
Class.Name-style domain individual as vocabulary. Caught by actually
running a live query and getting zero results instead of trusting the
"successful" parse — the file still parsed as valid Turtle throughout,
parsing alone would never have caught this. Separately, the two Python
scripts (compute_confidence.py, promote_to_rdf.py) and the CI workflow
all construct vocabulary URIs via TGS["ConfidenceType.Curated"]-style
Python code, which the text-based rename never touched at all — required
a second, manual pass adding a THINKER namespace object to each. A third
subtle bug: query_examples.sparql's query #2 appeared to work after the
first fix, but only because it was tested against a Python graph object
that still had thinkr: bound from an earlier parse() call in the same
session — the file itself was missing its own PREFIX thinkr: line, which
would have failed immediately in the Oxigraph CLI (no such inherited-binding
behavior there). Caught by re-testing against a deliberately fresh graph
with zero inherited bindings, not by trusting the first "passing" result.
General lesson, worth remembering for any future refactor of this
scale: parsing successfully and querying correctly are different bars —
verify against both, and be suspicious of a first success if the test
setup could be leaking state from earlier in the same session.
Two-repo structure planned, NOT yet executed: ontology as its own repo
(clean split while small — 575 triples now, cheapest this will ever be),
data repos per thinker (nate-hagens-kg continues as Hagens' data repo
once the ontology moves out), a future crosswalk repo for links whose
endpoints span two domains (also where tgs:convergesWith — see backlog
— eventually lives, since Stoicism↔Bhagavad Gita is arguably crosswalk
content even pre-HCR, not really "Hagens data" either). MJSullivan decided:
manual version pinning between repos (not git submodules — simpler,
upgradable later if needed), and defer actually splitting the crosswalk
repo + viewer tool into their own repos until there's real multi-domain
content to justify it (folders within nate-hagens-kg for now — zero cost
to defer since nothing exists in either yet). The ontology repo split
itself (physically separating data/seed/tgs-core.ttl into its own
repository) is the one piece still NOT done as of this writing — the
namespace rename happened first since it was more urgent (every hour of
delay meant more content minted under the wrong prefix), the physical
repo split is a mechanical follow-up.
Also raised, not yet resolved: reliability tiers may need to be
domain-scoped per claim-type rather than one fixed tier per Source —
e.g. Hagens as Reputable (generalist synthesist, not a credentialed
domain expert across everything he covers) vs. Richardson as plausibly
Authoritative specifically on American political/constitutional history
(she's a credentialed, peer-reviewed historian). Deliberately deferred
until real HCR content exists to test the distinction against, rather than
designed in the abstract now.
These are the load-bearing choices — worth understanding before making structural changes:
0a. Governance principle (2026-07-11): one class, one file. Every class
with instances gets its own complete, self-contained TTL file (class
declaration + all its individuals) — concepts.ttl, persons.ttl,
schoolsofthought.ttl, organizations.ttl, academicinstitutions.ttl,
linknotes.ttl, evidences.ttl, subjects.ttl. Small
supporting/controlled-vocabulary classes (ConfidenceType,
ReliabilityType, PolarityType) and ALL property declarations
live together in tgs-core.ttl — MJSullivan's framing: tgs-core.ttl
is the direct successor to the old ontology/schema.ttl, which can be
thought of as its rough prototype. Classes with zero instances yet
(Work, Episode, Source) stay declared in tgs-core.ttl until
their first real instance, at which point they should be promoted to
their own file. External resource mappings get their own dedicated
file too (crosswalknotes.ttl as of 2026-07-11, replacing separate
dbpedia_links.ttl/wikidata_links.ttl — see the CrosswalkNote
design decision below) — this same
principle extended to "declared external resources." Crosswalk files
(once any exist — e.g. for convergesWith) should be named by
subject + relationship, not yet needed since nothing's been built
there. Every file owl:imports tgs-core.ttl, resolved locally via
data/seed/catalog-v001.xml (Protege can't resolve
http://example.org/thinkr# over the network — it's not a real URL —
so the catalog maps it to the local file). This means any single file
can be opened standalone in Protege and "behave reasonably well," per
MJSullivan's explicit goal — was NOT true before this reorganization,
when only ontology/schema.ttl was really Protege-friendly on its own.
0b. Governance principle (2026-07-11): OWL class depth capped at 2-3
levels; deeper refinement is SKOS's job, not OWL's; category classes
end in "Type" or "Kind." Stated explicitly by MJSullivan after the
NamedEntity/PersonEntityType restructure. Concretely: OWL
rdfs:subClassOf chains stay shallow (NamedEntity -> Person is 2
levels; NamedEntityType -> PersonEntityType is 2 levels — both
compliant as built). If a class needs genuine hierarchical NUANCE
beyond that depth, the right tool is SKOS skos:broader/narrower
between INDIVIDUALS of a category class (exactly how Subject
already works — official subjects and derived sub-topics are both
skos:Concept individuals related via skos:broader, not nested OWL
subclasses) — not deeper OWL subclassing. Refinement per subclass
happens via a dedicated Categorization pattern (a class ending in
"Type"/"Kind", paired with a has*Type property that's
rdfs:subPropertyOf a more general one) — this is exactly the
PersonEntityType/hasPersonEntityType pattern just built, and the
template for any future subclass's equivalent (e.g. a hypothetical
OrganizationEntityType/hasOrganizationEntityType).
RETROACTIVE NAMING QUESTION, NOT YET ANSWERED: ConfidenceType,
ReliabilityType, PolarityType predate this rule and don't end
in "Type"/"Kind." Renaming them would be a large, disruptive job —
they're referenced throughout every LinkNote/Evidence/Source
and in compute_confidence.py's core logic. Flagged, not resolved —
do NOT rename these without an explicit go-ahead, this is a
meaningfully bigger change than anything done under this rule so far.
GOOD SIGN, not a fix needed: thinkr:Subject's design already
followed the "taxonomy belongs in SKOS" half of this principle before
it was ever stated as a formal rule — worth noting as validation the
instinct was already right.
0c. Governance principle (2026-07-11): crosswalk assertions need their
own provenance — thinkr:CrosswalkNote. A bare owl:sameAs triple
has nowhere to record WHEN it was verified or WHAT caveats apply —
real problem, not hypothetical: this session hit two cases (Nate
Hagens' Wikidata entry actually being a different person; Iain
McGilchrist's name needing correction from what NER extracted) where
that context mattered and had nowhere structured to live except a
giant prose comment. Fix: reuse the already-proven thinkr:LinkNote
sidecar pattern rather than invent something new — keep the actual
owl:sameAs triple direct and reasoner-compatible (nesting the
relationship inside a blank node would have broken its formal
semantics), and add a separate thinkr:CrosswalkNote individual
referencing the same entity+URI pair via thinkr:aboutEntity/
thinkr:aboutExternalURI, carrying thinkr:crosswalkSource
(DBpedia/Wikidata), thinkr:verifiedOn (date), and skos:scopeNote
(reused directly, not redeclared — same property already used for
superclass/superproperty documentation).
FILE-ORGANIZATION QUESTION RESOLVED AS A SIDE EFFECT: this superseded
an earlier live discussion about splitting dbpedia_links.ttl/
wikidata_links.ttl by SUBJECT class (PersonCrosswalk.ttl,
SchoolCrosswalk.ttl, etc.) instead of by TARGET database. Once
CrosswalkNote became a real class with real substantive content
(not just a bare triple), the natural home was its own single file —
crosswalknotes.ttl — same "one class, one file" reasoning as why
linknotes.ttl bundles its relation triples rather than splitting by
Concept vs Person vs School. Both dbpedia_links.ttl and
wikidata_links.ttl fully dissolved into it, all 37 existing
owl:sameAs triples (32 DBpedia + 5 Wikidata) retrofitted with real
CrosswalkNotes in one scripted pass, tested via a live query
(Iain McGilchrist's DBpedia+Wikidata crosswalks, both correctly
surfacing his name-correction scopeNote) before shipping.
0d. Naming convention, SUPERSEDED then finalized (2026-07-11):
Source_Number_TitleFragment, prefixed by the generic EpisodeType,
not the source's own branding. Full pattern:
tgs:<EpisodeTypeName>.<SourceAcronym>_<Number>_<TitleFragment> —
e.g. tgs:Interview.TGS_226_CanMoneyServeLife,
tgs:Monologue.TGS_134_AWorldAtTheEdgeOfChange.
THREE separate corrections stacked into this final form, each worth
understanding:
(1) Original version used Episode/Frankly as the local-name
prefix — Frankly being TGS's own branding for the Monologue format.
This directly contradicted the reason thinkr:EpisodeType was built
generic in the first place (TGS-specific terms belong in
skos:altLabel only, e.g. "frankly"/"roundtable" — see the
EpisodeType section above). Fixed by using the EpisodeType's own name
(Interview/Monologue/PanelDiscussion) as the prefix instead —
thinkr:Episode stays the actual rdf:type regardless; this is
purely about what the IRI itself looks like.
(2) MINTING COLLISION RISK, identified same day once it became clear
other sources (podcasts, vlogs, books, TED talks, PhD theses, etc.)
will eventually get modeled too: a bare episode NUMBER has no
uniqueness guarantee across sources — confirmed concretely the same
session when Matthew Monahan's own show ("The Regeneration Will Be
Funded") surfaced as a second real source with its own numbering.
Fixed by requiring a SourceAcronym component (TGS, and eventually
others as they get modeled) — uniqueness comes from
SourceAcronym + Number alone, the title fragment is PURE human
readability and can be shortened or dropped without breaking
anything. No GUIDs (every component stays human-mnemonic), but no
silent collision risk either.
(3) Source acronyms need to be a genuinely controlled list, not an ad
hoc string each session invents fresh — currently just documented
here (TGS), not yet backed by a formal registry or tied to real
thinkr:Source individuals. Revisit if/when this actually causes a
real collision or confusion, not before — same "don't build ahead of
real need" instinct as everything else tonight.
Applied retroactively to all 7 existing Episode individuals same
session (cheap now, would only get more expensive later) — see the
3-step rename in episodes.ttl's git history if the exact mechanics
are ever needed again.
0e. Design decision (2026-07-11): thinkr:School split into
SchoolOfThought/Organization/AcademicInstitution. Raised by
MJSullivan as genuinely overloaded, confirmed by checking the actual
data rather than assuming — all 12 existing School individuals
split cleanly into intellectual movements (7: Stoicism,
DoughnutEconomics, PeakOilMovement, BehavioralEconomics,
DegrowthMovement, SystemsEcology, CivilRightsMovement) and real
legal entities (5: TheOilDrum, PostCarbonInstitute, MaEarth,
BiomeTrust, ConsilienceProject). Third category,
AcademicInstitution, added on MJSullivan's own concrete test — "an
Organization might sponsor Nate, an Academic Institution never would"
— genuinely different relationship verbs, not just a stylistic
split. First real AcademicInstitution instances: University of
Vermont, University of Chicago, University of Minnesota — previously
only ever in prose inside Person.NateHagens's own comment, never
modeled as individuals. All three new classes rdfs:subClassOf thinkr:NamedEntity (exactly the extension point that class was
built to support), siblings of each other and of Person, NOT
nested — AcademicInstitution under Organization would have
blurred the exact distinction that motivated splitting them.
memberOf's range widened from thinkr:School to a owl:unionOf
the three new classes. Executed as one scripted, verified pass — 32
cross-file references renamed via text substitution (safe here,
each old name a distinct non-overlapping string), confirmed zero
stray references remained, confirmed the convergesWith bidirectional
link (which specifically referenced DoughnutEconomics) still
resolved after the rename, confirmed via live query that the 7/5/3
split landed exactly as counted beforehand.
0f. Architectural revision (2026-07-14): thinkr:Persona introduced
as the graph's primary unit of analysis; thinkr:Human demoted to a
pure biographical leaf node. The single biggest structural change
made in this project to date — worth understanding before touching
any Human/Persona-adjacent file. Full blow-by-blow lives in
docs/sidecar-cleanup-handoff.md (the "SESSION CUT OFF (2026-07-14)"
entry and everything below it); this is the load-bearing summary.
**The principle, stated plainly:** there are no Human-to-Human
relationships anywhere in this graph. `thinkr:actsThrough` (Persona
-> Human) is the ONLY property that ever touches a `Human` from the
relationship side. Every other relationship — who hosted/guested an
Episode, who has a professional/personal `Relationship`, which
external database a `CrosswalkNote` maps, and critically, which
`Concept` a thinker influenced or echoed or applies to — runs through
`Persona`, never `Human` directly. `Human` holds only genuine
biography: legal name, birthdate, earned credentials, employment
history. This inverts what most general-purpose ontology backgrounds
(FOAF, schema.org, DOLCE) would assume — Person/Human as the
load-bearing entity, roles as annotations on top. That assumption is
backwards FOR THIS PROJECT SPECIFICALLY: a thinker ontology's entire
job is modeling public intellectual reach, and every fact this graph
actually cares about (who influenced whom, who guested with whom,
who's affiliated with which institution) is definitionally about the
public role, never the private biography. `Human` still exists
underneath (biographical anchor) — it just correctly stops being
architecturally central.
**Why this matters more than it sounds like it should:** the
`Concept`->thinker connection (`influencedBy`/`echoesIdeaOf`/
`contrastsWith`/`appliesTo`, and `CrosswalkNote.aboutEntity`) is the
actual reason `LinkNote`/`Evidence`/`compute_confidence.py` exist at
all — it's the graph's real core mechanism, not a peripheral detail.
This was flagged as "not yet executed, not even fully scoped" across
two separate earlier sessions before finally landing 2026-07-14.
**Executed as six sequential, independently-validated batches**
(schema -> minimal Persona shells for all 25 existing Humans ->
Episode hasHost/hasGuest -> full relationships.ttl rebuild
(including subjectRole/objectRole replacing the old single-role
design, and dcterms:date honesty-over-fabrication) -> folding in
organization/school relationships -> the core Concept-facing
migration) plus a follow-on file reorganization splitting
`thinkr:Category`-marked classes and their enumerated individuals out
of `tgs-core.ttl` into `enumerations.ttl` (one-class-one-file
restored for that family too). Every batch validated via live SPARQL
query against the running Oxigraph store before being considered
done, not just parsed successfully — "it parses" and "it's correct"
are different claims, learned the hard way mid-session when a
class/instance file split was initially built wrong and caught only
by actually reading the output, not trusting the script ran without
an error.
**Two real bugs caught and fixed as a byproduct, not the point of
the exercise:** three guest `Relationship` individuals had silently
lost their actual property declarations in an earlier, unrelated
rebuild (only bare references survived); `relationships.ttl` was
missing 4 of Daniel Schmachtenberger's 7 confirmed `episodes.ttl`
guest appearances, a different incomplete subset than an earlier
prototype file had independently built.
**What's still open, deliberately not resolved 2026-07-14:** which
specific `foaf:homepage` links (Substack/personal site/LinkedIn)
belong on `Persona` vs. stay on `Human` — a real judgment call, not
yet made. `thinkr:memberOf` stays flat on `Human` for now rather than
being fully converted to `Relationship` individuals — coexists with
the structured version rather than being superseded by it, confirmed
intentional via the University of Minnesota case, which correctly
carries both.
-
Emerging principle (2026-07-11, not yet fully tested): OWL for formal relationships, SKOS for informal ones. Named explicitly by MJSullivan after noticing the pattern already forming —
thinkr:Person/Concept/Evidence/Source/ConfidenceTypeetc. are OWL classes with strict, single-purpose axioms (aLinkNotehas a validcalculatedConfidenceor it doesn't — no ambiguity, no multi-parent messiness), while the Subject taxonomy (seedata/seed/subjects.ttl) is SKOS specifically because real-world topic classification is genuinely multi-parent and fuzzy (a Concept can legitimately belong under more than one Subject). Deliberately NOT formalized as a hard rule yet — "let's see what the data tells us" was the explicit call, consistent with this project's general bottom-up-from-real-need methodology (see the earlier bottom-up-vs-top-down content strategy discussion). Treat this as a lens for judgment calls going forward (does a new relationship need OWL's strictness, or SKOS's looser semantics?), not as a rule to enforce mechanically before there's enough content to test it against. relationships, SKOS for informal ones.** Named explicitly by MJSullivan after noticing the pattern already forming —thinkr:Person/Concept/Evidence/Source/ConfidenceTypeetc. are OWL classes with strict, single-purpose axioms (aLinkNotehas a validcalculatedConfidenceor it doesn't — no ambiguity, no multi-parent messiness), while the Subject taxonomy (seedata/seed/subjects.ttl) is SKOS specifically because real-world topic classification is genuinely multi-parent and fuzzy (a Concept can legitimately belong under more than one Subject). Deliberately NOT formalized as a hard rule yet — "let's see what the data tells us" was the explicit call, consistent with this project's general bottom-up-from-real-need methodology (see the earlier bottom-up-vs-top-down content strategy discussion). Treat this as a lens for judgment calls going forward (does a new relationship need OWL's strictness, or SKOS's looser semantics?), not as a rule to enforce mechanically before there's enough content to test it against. -
Curated vs. candidate confidence tracking is the most important design decision in the whole project. An LLM can propose hundreds of plausible-sounding parallels between TGS concepts and historical thinkers, but most surface pattern matches ("both talk about balance!") aren't intellectually honest links. Nothing should present as a real connection to an end user until a human has reviewed it. This is enforced structurally:
tgs:confidenceis anowl:ObjectPropertypointing to one of exactly two enumerated individuals,tgs:ConfidenceType.Curatedortgs:ConfidenceType.Candidate(seedata/seed/tgs-core.ttl— closed enumeration viaowl:oneOf). CI actively validates everytgs:LinkNotehas one of these two values, not just that some value is present. -
Every individual follows
<Domain>:<Class>.<Name>IRI minting (e.g.tgs:Person.MarcusAurelius,tgs:Concept.Overshoot,tgs:LinkNote.DiscountMarcus) and is explicitly typeda tgs:SomeClass, owl:NamedIndividual— this is MJSullivan's established convention from his other ontology work, adopted here for consistency. Classes and properties themselves (indata/seed/tgs-core.ttl) are NOT renamed under this scheme — only individuals/instances are. -
skos:prefLabelinstead ofrdfs:labeleverywhere, in anticipation of usingskos:altLabelfor alternate names down the line. Applies to both schema-level (class/property names) and individual-level data, for consistency. -
All natural-language literals get an
@entag, in anticipation of future multi-language support. EXCEPTION: controlled-vocabulary / enum-style values (like the oldtgs:confidencestrings, now replaced by actual individuals per point 1) should NOT get language tags — they're codes, not prose to translate. -
owl:sameAslinks to BOTH DBpedia and Wikidata on people/schools where possible — DBpedia for broad, low-effort Wikipedia-mirrored coverage; Wikidata added deliberately per-entity because it's more actively maintained and has its own SPARQL endpoint. 5 of ~26 people/schools have verified Wikidata links as of 2026-07-11 (EOWilson, Marcus Aurelius, Post Carbon Institute, Iain McGilchrist, Daniel Schmachtenberger) — seedata/seed/crosswalknotes.ttl(filter bythinkr:crosswalkSource thinkr:CrosswalkSource.Wikidata). CRITICAL: Wikidata Q-numbers have no mnemonic structure and are genuinely easy to get wrong (a plausible-looking search hit for "Marcus Aurelius Antoninus" is actually a disambiguation page, Q1632736 — the real entity is Q1430). Never copy a Q-number from memory; always verify the description on the search hit actually matches the intended person before adding. -
Concept/School definitions are paraphrases, not verbatim Hagens quotes — copyright and accuracy reasons. They're first-draft glosses based on public descriptions of his framework, not checked against actual transcripts/book text. Worth tightening against primary sources over time, not urgent.
- 719 triples total (
make validateshould confirm this exactly) - 19 Concepts, 21 People (added Kate Raworth), 7 Schools (added Doughnut Economics), 15 LinkNotes (14 curated, 1 deliberately-marked candidate as a review-queue example), 15 Evidence individuals, 1 Source individual (the CalDEC California Doughnut Report — first real use, was empty until 2026-07-11), 1 Work individual (same report — also first real use), 12 Subject individuals (6 official + 6 derived sub-topics, all 19 Concepts tagged)
- Files, one class per file (governance principle, 2026-07-11) — see
README.md's Structure section for the full rationale and per-file
descriptions.
data/seed/tgs-core.ttlis the shared vocabulary; every other fileowl:importsit viacatalog-v001.xml. The oldontology/schema.ttl,data/seed/people.ttl, anddata/seed/expansion_2026-07-10.ttlno longer exist — fully dissolved into the new per-class files by a scripted, triple-count-verified repartition (673 → 678 after adding the new ontology/import declarations themselves). scripts/compute_confidence.pyis the derivation engine forcalculatedConfidence— re-run it after ANY Evidence edit, never hand-set that property
DONE 2026-07-10 — Evidence/claim provenance model. Built: tgs:Evidence
and tgs:Source classes, tgs:ReliabilityType (Authoritative/Reputable/
Unverified/Unreliable) and tgs:PolarityType (Supports/Contests/Mentions)
enumerations, ConfidenceType expanded to 4 values (added Corroborated,
Disputed). tgs:confidence moved from LinkNote to Evidence (has THIS piece
of evidence been reviewed?); new tgs:calculatedConfidence on LinkNote is
DERIVED via scripts/compute_confidence.py — never hand-set. Aggregation
rule implemented exactly as designed (see that script's docstring): only
Curated evidence counts; Unreliable-tier sources excluded regardless of
polarity (the eugenics-publication case); superseded evidence
(prov:wasRevisionOf) excluded (the Neanderthal-taxonomy case); any
Reputable-or-better Contests evidence forces Disputed, overriding
Corroborated; 2+ independent Reputable-or-better Supports -> Corroborated;
Unverified-tier support alone can never reach Corroborated regardless of
volume. All 5 rule branches individually stress-tested and passing before
shipping (not just happy-path tested). All 14 existing LinkNotes migrated —
6 got real prov:generatedAtTime years extracted directly from their
existing rationale text (Catton 1980, Tainter 1988, Meadows/Limits to Growth
1972, Hubbert 1956, Jevons 1865); the other 8 deliberately left without a
timestamp rather than fabricating one. promote_to_rdf.py and CI both
updated and re-tested end-to-end under the new model.
FOLLOW-UP NOT YET DONE: the DuckDB staging tables (extraction/ init_staging_db.py) were NOT extended for Evidence-level review — they
still operate at the old LinkNote-level granularity. Once real Evidence
accumulates (multiple pieces per LinkNote, actual Source records), the
staging/review workflow will need its own Evidence-aware tables. Also:
data/seed/ has zero tgs:Source individuals yet — every migrated Evidence
is unsourced (implicit Reputable tier) — so Corroborated/Disputed have never
actually been exercised on real data, only on the isolated test script.
MEDIUM (downgraded from HIGH, 2026-07-12) — Physically split the
ontology into its own repo. Repo renamed same day:
nate-hagens-kg → thinkr-nate-hagens-kg (folder-level only, git
history unaffected — mv doesn't touch .git/). Convention going
forward: every thinkr:-related repo starts with thinkr- (a future
ontology repo would be thinkr-core, sibling to
thinkr-nate-hagens-kg, not nested inside it).
Downgraded from HIGH because the actual trigger for needing this — a
second thinker's graph (Heather Cox Richardson, per the original
reusable-methodology goal) — doesn't exist yet. Revisit as HIGH again
once that experiment actually starts.
NOT just a folder move when it does happen — three concrete breakage
points identified 2026-07-12, worth having ready rather than
rediscovering:
make validate,compute_confidence.py, the CI check, andpromote_to_rdf.py's label-loading allglob.glob('**/*.ttl', recursive=True)scoped to the current repo — the momenttgs-core.ttl(rename tothinkr-core.ttlat the same time, see below) leaves this repo's directory tree, all of them silently stop seeing its class/property declarations. Validation would still technically "pass" (rdflib doesn't error on undeclared classes) while validating an incomplete picture.catalog-v001.xmlmaps thethinkr:namespace to a same-directory relative path — breaks the moment the target file isn't a sibling.- GitHub Actions CI checks out only the one repo it's defined in —
needs explicit updating to also check out the sibling
thinkr-corerepo, or it'll validate against a broken subset with no warning. Also worth doing at the same time, not a separate rename later:tgs-core.ttl→thinkr-core.ttl. Its own content is already almost entirelythinkr:namespace (classes, properties, Category enum individuals) — the current name is actively misleading about what's inside it. File-level reorganization already done (2026-07-11):tgs-core.ttlis a genuinely separate, self-contained file, and every primary class has its own complete file withowl:importspointing at it — this groundwork makes the eventual repo split mechanically easier (just move the one file +catalog-v001.xml+ update the three breakage points above) even though the actual cross-repo split hasn't happened yet.
DONE 2026-07-11 — Cross-tradition "convergent parallel" property. Built
as thinkr:convergesWith (owl:ObjectProperty, owl:SymmetricProperty) in
tgs-core.ttl. First real use: tgs:Concept.Overshoot ↔
tgs:SchoolOfThought.DoughnutEconomics (Kate Raworth's framework, applied
regionally by CalDEC — see their CC-BY-licensed 2025 California Doughnut
Report, which independently uses "overshoot" as a technical term). Work
and Source — both declared since the original schema but empty until now
— got their first real instances and were promoted to their own files
(works.ttl, sources.ttl) per the one-class-one-file governance
principle. Full Evidence-backed provenance chain built: LinkNote →
Evidence (Curated, Supports) → Source (Authoritative tier, DOI-
registered) → correctly derives calculatedConfidence: Curated via
compute_confidence.py, tested end-to-end.
CORRECTION CAUGHT DURING BUILD: owl:SymmetricProperty is a FORMAL OWL
semantic — it does NOT make the reverse direction queryable without a
reasoner. Verified empirically: querying from School.DoughnutEconomics
side returned zero results until the reverse triple was added by hand.
The property's own original comment claimed the opposite ("no need to
assert both directions") — wrong, and corrected in tgs-core.ttl once
caught. Both directions must be explicitly asserted for every future
convergesWith use — this will not be automatic no matter how many
times it's tempting to assume OWL semantics mean SPARQL will "just know."
Case chosen (Stoicism ↔ Bhagavad Gita) was NOT what ended up triggering the
build — CalDEC/Doughnut Economics came up first in practice. Stoicism ↔
Gita remains a good candidate for the second use of this property whenever
it comes up again, same reasoning as originally logged.
CORRECTION LOGGED 2026-07-10 — blank nodes are NOT the right tool for
either item above. Blank nodes were briefly proposed for provenance —
this would be a mistake. They have no stable identity across file
re-saves/merges/different-tool-loads, which is exactly why tgs:LinkNote
was deliberately built as a NAMED individual in the first place (the
curated/candidate review workflow depends on referencing one specific claim
reliably over time — approve it in DuckDB, promote it, audit it later, diff
it cleanly in git). Blank nodes remain fine for genuinely anonymous
structural scaffolding only — e.g. the existing owl:oneOf list inside
ConfidenceType's enumeration, which nothing will ever need to reference
by identity.
MEDIUM — Source reliability isn't a single fixed scalar (expanded 2026-07-11). Three related gaps, all really the same underlying insight — a Source's standing isn't one static number, it varies by domain, by time, and by relationship to the specific subject:
- DOMAIN-SCOPED (original note): one fixed
ReliabilityTypeperSourcedoesn't fit real epistemics — Hagens is plausiblyReputablegenerally but arguablyAuthoritativeon his own stated influences; Richardson would plausibly beAuthoritativespecifically on American political history. - TEMPORAL DECAY:
prov:wasRevisionOfalready handles explicit supersession (built and tested — the Neanderthal-taxonomy case), but evidence that's simply OLD, with nothing specifically superseding it, currently counts identically to evidence from yesterday. Recency itself should matter, not just explicit supersession. Source standing can also change over time independent of any specific claim (a source assessed as Authoritative in 2010 might not hold that assessment in 2026). - SOURCE-SUBJECT BIAS: distinct from both of the above and from
PolarityType— a Source can be reliable in general while having a demonstrated, patterned bias toward one SPECIFIC subject. Concrete example given: if a source has repeatedly, demonstrably shown antagonism specifically toward Nate Hagens, that source's negative claims ABOUT Hagens specifically warrant discount (or inversion — "it should inflate them, but that's another story" — a real, separate future consideration, not resolved here) — without blanket-downgrading that same source's reliability on unrelated subjects, which a flatReliabilityType.Unreliableon the Source would incorrectly do. This is a Source-Subject RELATIONSHIP, not a property of either alone. IMPORTANT DISTINCTION FROM THE CONTRIBUTOR/MENTION DESIGN: this is NOT asking the system to detect stance from text (that's the hard NLP problem already deliberately avoided in the Contributor rule). This is recording a stance a HUMAN has already determined — a genuinely more tractable problem, just not yet designed. ALL THREE deliberately deferred, consistently, same reasoning each time: exactly ONESourceindividual exists in the entire graph as of this writing, and zero real multi-period or biased-relationship examples to design any of these mechanisms against. Designing blind risks guessing the shape instead of discovering it — same trap the Concept-evolution-over-time item and the OWL/SKOS "let's see what the data tells us" principle were both meant to avoid. Revisit once real Source data with an actual multi-period or biased-relationship case exists. CONSTRAINT TO HOLD FIRM ON WHENEVER THIS IS BUILT: stays ORDINAL, not numeric — this project has repeatedly and explicitly rejected manufactured- precision scoring (see e.g. the confidence aggregation rule's own reasoning). A weighted numeric decay formula would be a real regression, not an enhancement, however tempting it looks on paper.
MEDIUM — Concept evolution over time isn't modeled. A thinkr:Concept's
skos:definition is a single string — if Hagens revises his framing of
something between an earlier episode and a later one, there's currently no
way to represent that the definition CHANGED, only whatever the latest
paraphrase currently says. The prov:wasRevisionOf pattern already built
for superseded Evidence (the Neanderthal-taxonomy case) is the obvious
template to reuse here, just never applied to Concept itself. Only
matters if tracking how Hagens' thinking has shifted over time is a real
goal, as opposed to "what does he currently believe" — raised during the
bottom-up-vs-top-down content strategy discussion, not yet prioritized.
MEDIUM — Social/professional graph layer ("who knows who"). Proposed
2026-07-11: a parallel layer tracking real-world professional/social
connections between people in the TGS/Doughnut world — starting with
everyone Nate Hagens has had on the show or mentioned. SCOPE NARROWED same
day: not full employment history — specifically publications contributed
to and credentials relevant to TGS/Doughnut Economics. This actually needs
little new schema: thinkr:Work already exists (see works.ttl, first
real instance already built for the CalDEC report) and Dublin Core's
dct:creator/dct:contributor already cover "who contributed to this
publication" — pure reuse, no new classes needed for that half. Credentials
specifically still needs a real example before designing the property
("Policy Lead, CalDEC" vs "PhD Ecological Economics" probably want
different shapes) — same "let's see what the data tells us" principle as
the OWL/SKOS split, not designed in the abstract.
LINKEDIN, RESOLVED: manual login-and-screenshot is fine, meaningfully
different from automated scraping (which remains off-limits per LinkedIn's
ToS). Most published authors have CVs elsewhere too, so LinkedIn ends up
being one manual source among several, not the sole one. Privacy judgment
call is about what gets PUBLISHED in the graph, not how it was gathered —
still worth per-person consideration for non-public-figure guests even
though the collection method itself is now clean.
CLASSIFICATION RULE RESOLVED 2026-07-11: Contributor IFF their publications
align with the TGS/Doughnut movement; otherwise, just a "mention." Cleaner
than the ally/critic axis first proposed — sidesteps sentiment/stance
detection entirely (a hard NLP problem, not worth bolting onto the rough
extraction/index_named_entities.py triage tool) by grounding the
classification in something objective and already-scoped: does this
Person have a thinkr:Work (via dct:creator/dct:contributor) that
actually aligns? Elon Musk correctly falls out as "mention" — no aligned
publications, no stance-detection needed. Also correctly handles the
mirror case (someone with publications explicitly ARGUING AGAINST
degrowth/limits thinking) without a separate "critic" category — they
simply don't qualify as Contributor either.
OPEN WORLD ASSUMPTION CORRECTION, same day: the rule as first stated is a
CLOSED-world rule and would be wrong if implemented literally in RDF —
absence of a Work in the graph doesn't mean absence of one in reality, it
might just mean it hasn't been catalogued yet. Fix, reusing a pattern
already proven elsewhere in this project (ConfidenceType.Candidate as
an epistemic floor state, not a confirmed negative — same shape as
Disputed needing to be an ACTIVE assertion, not a default): Contributor
must be a positively-asserted status, made true only when a qualifying
Work is actually confirmed. "Mention" is NOT a status to assert at
all — it's just the absence of a Contributor assertion. Anyone with a
Person individual lacking that assertion is honestly "not yet
established as Contributor," freely upgradable the moment a real
publication surfaces, rather than a claim that would ever need retracting.
Known, accepted consequence of the underlying rule (unaffected by the OWA
fix): a genuine personal/professional connection with no aligned
publications (a collaborator, an activist without formal writing) also
never gets a Contributor assertion — consistent with narrowing this
layer to publications-and-credentials rather than broader social
proximity.
THIRD STATUS ADDED 2026-07-11: InvitedGuest, proposed for people Nate has
personally had on the show who may lack a citable aligned publication (a
guest with no formal CV is unlikely but possible). Fits the same
positively-asserted, OWA-safe pattern as Contributor — NOT a rung between
Contributor and Mention on a ladder, an independent fact with its own
evidence. Not mutually exclusive with Contributor — someone can hold both.
Precision worth keeping: WHETHER someone was a specific episode's featured
guest is objectively checkable (in download_manifest.csv's episode
metadata) and can be Authoritative-tier reliable; the SIGNIFICANCE of being
invited is Nate's own editorial judgment, which the graph can report as a
fact about his selection process but can't itself verify — same
reliability/significance split the ReliabilityType/PolarityType model
already handles elsewhere.
PRACTICAL SHORTCUT: likely mostly derivable from data already downloaded,
not new manual research — every type=interview episode's title in
download_manifest.csv typically has the guest's name baked in
(224-rob-hopkins, 226-matthew-monahan). Frankly episodes (solo) don't
apply; this is an interview/roundtable-type signal specifically.
Still open: does this deserve its own file/data layer, given it's a
different research question than the idea-linking work? Not started —
logged for discussion, not build-ready yet.
MEDIUM — Sidecar naming/structure cleanup. Proposed 2026-07-11 by
MJSullivan. Full details, worked examples, and — critically — the real
open questions NOT yet resolved, in docs/sidecar-cleanup-handoff.md.
Confirmed conventions: underscore between the two cross-referenced
entities, person/org always first, drop the "Note" suffix from class
names, stop using "Crosswalk" terminology (reserve it for a genuine
future cross-ontology alignment concept). NOT confirmed, needs real
decisions before execution: whether hasSubject/hasObject/
hasRelationshipType describes a renamed LinkNote or an actual
merge of LinkNote+Evidence; whether hasRelationshipType replaces
the current multiple-specific-relation-properties design
(echoesIdeaOf/influencedBy/etc.) with one generic property + type
value (a real architectural change, not just a rename); the exact
replacement names for LinkNote, CrosswalkNote, and the not-yet-built
AffiliationNote. Scope: 67 individuals across linknotes.ttl (15),
evidences.ttl (15), crosswalknotes.ttl (37) as of this writing.
Execute the same way as every other large rename this session — scripted
and verified, not manual — once the open questions are actually settled.
ESCALATED same day: Subject_Object naming assumes at most one
relationship-instance per entity pair, which is often false (Aristotle
may have discussed money in multiple works; Nate's UMN affiliation may
be multiple distinct periods/roles, not one continuous thing). Genuinely
harder than the naming cleanup above — no comprehensive solution yet per
MJSullivan's own assessment. Full candidate directions in the handoff
doc's own "ESCALATION" section. Do not resolve in the abstract — wait
for a real case needing it and design against that.
ALSO RESOLVED same day, fold into the same rework: drop the direct
owl:sameAs triple on CrosswalkNote-linked entities entirely, rely
solely on aboutExternalURI — owl:sameAs formally implies full
bidirectional property inheritance under a reasoner (the well-known
"sameAs problem"), not something actually meant here; skos:exactMatch
considered and rejected as formally Concept-scoped. See handoff doc's
"4b" for full reasoning.
CRITICAL CAVEAT, MJSullivan's own words: "not 'seeing' how real data
should look while leveraging the pattern developed... need a concrete
set of inter-related examples to finally 'get it'. At the moment, I
don't." The handoff doc's isolated before/after examples, discussed one
at a time, have NOT achieved actual clarity — a genuinely narrative,
multi-sidecar worked example (ideally confronting the multi-instance
problem with real values, not placeholders) is still needed before this
document should be treated as execution-ready. See the doc's own CAVEAT
section, placed prominently at the top for exactly this reason.
MEDIUM — Wikidata verification. 8 of ~35 people/schools/orgs/institutions
now have verified Wikidata owl:sameAs links as of 2026-07-13 (Aristotle,
Gautama Buddha, Martin Luther King Jr. added this session, alongside the
earlier 5) — pattern established in data/seed/crosswalknotes.ttl, just
needs the remaining per-entity verification legwork — see design decision
#5 above; Q-numbers are NOT mnemonic and guessing from memory is
genuinely risky.
MEDIUM — New person-bootstrap candidates from entity index refresh (2026-07-13). After running the Roundtable/Frankly downloads and re-indexing (166 documents now known, 18 newly indexed), two names surfaced with real, already-confirmed connections to the graph, not just plausible cold-start candidates:
- Nora Bateson (100 mentions, 9 docs) — Gregory Bateson's daughter;
Gregory Bateson is already in the graph via
Concept.Metacrisis'sinfluencedBylink. Real, findable family/intellectual lineage. - Zak Stein (65 mentions, 3 docs) — a real, already-encountered name:
Daniel Schmachtenberger's own LinkedIn post about the Civilization
Research Institute specifically named "Zachary Stein" as a colleague
there, found during his own bootstrap earlier this session.
Other real, substantial figures worth a bootstrap pass, lower priority
since no pre-existing graph connection confirmed yet: Tom Murphy
(physicist, "Do the Math" energy-limits blog — thematically close to
TGS's core thesis), Shanna Swan (epidemiologist, fertility/endocrine
research), Nancy McWilliams (psychoanalyst), Linda Birnbaum (toxicologist,
former NIEHS director), Theo Dawson (developmental psychology).
Full current worklist always available via
python extraction/top_persons.py.
MEDIUM — Primary-source verification. Concept/School definitions are first-draft paraphrases (design decision #6), never checked against actual Hagens transcripts/book text. Not urgent, but a real accuracy gap.
DONE 2026-07-11 — tgs:Episode now has real instances. 7 total, all
EpisodeType.Interview so far, promoted to episodes.ttl — see the
naming-convention section above for the full minting-scheme story. Trigger
was making explicit how bootstrapped guests relate to Person.NateHagens
directly (dct:creator/dct:contributor), not left implicit via
PersonEntityType.Guest alone. Monologue-type (Frankly) and
PanelDiscussion-type (Reality Roundtable) instances still don't exist —
same "no concrete trigger yet" status as before, just narrower in scope
now that Interview-type is resolved.
LOW — tgs:memberOf consistency question. Doesn't chain up to
tgs:relatesTo the way the concept-to-person properties do
(echoesIdeaOf, influencedBy, etc. all do). Flagged once, never
resolved either way — low stakes, revisit whenever convenient.
LOW — foaf:firstName/foaf:family_name deliberately deferred. Proposed
2026-07-11 during Person bootstrapping — deferred, not rejected. Vocabulary
already available for free (Person already subclasses foaf:Person), so
this costs nothing structurally whenever it's actually needed. NOT added
now because "obvious" breaks down fast on people already in the graph:
Marcus Aurelius (Roman praenomen/nomen, not modern first/last), William R.
Catton Jr. (suffix breaks a clean split), Aristotle (single name, no split
exists), Nate Hagens (is "Nate" the name to record, or "Nathan" with Nate
as how he's known — a judgment call, not a mechanical split). Adding it
now only for easy modern names would create inconsistent coverage that
looks like an oversight, not a deliberate choice. Trigger for actually
building it: a real downstream need (sortable/alphabetized browse view,
name-based dedup — which would also need to solve the "Nate" vs "Nate
Hagens" alias problem already flagged from the entity index).
LOW — LLM extraction plan never run for real. extraction/README.md's
aspirational Steps 3-6 (LLM concept/link mining, distinct from the
transcript-download and entity-triage infrastructure documented in the
same file, which IS built and working) has only been tested end-to-end
with fake staging data to confirm the DuckDB→promote_to_rdf.py→graph
pipeline mechanically works — never run against actual transcripts yet.
FUTURE, not yet actionable — dedicated Claude Skill. MJSullivan intends to
eventually build one (mirroring the uwom-ontology skill in his other
uwom-kg repo, renamed from knowledge-graph on 2026-07-10) once this project's patterns feel more settled.
Natural candidate content based on what's already stable: the IRI minting
convention (design decision #2), the curated/candidate review discipline
(design decision #1, soon to be superseded by the Evidence model above),
the DBpedia+Wikidata verification workflow (design decision #5). Explicitly
NOT worth building prematurely — revisit once there's been another round or
two of real content expansion and the patterns (especially the Evidence
model, if built) have proven durable rather than still shifting.
LOCATION CLARIFIED 2026-07-12: lives OUTSIDE any single git repo entirely
— same pattern as uwom-ontology itself, which sits in Claude's own
skill-loading system (/mnt/skills/user/), not checked into uwom-kg's
git history. Not tied to thinkr-core, thinkr-nate-hagens-kg, or any
future thinkr--prefixed repo specifically — loaded into any session
regardless of which repo is open that day. Practical consequence for
content: needs to be the DISTILLED, thinker-agnostic half of what's in
this file only — one-class-one-file governance, the IRI conventions, the
LinkNote/Evidence/CrosswalkNote sidecar pattern, the OWL-depth-cap/
SKOS-for-taxonomy split, the bootstrap-a-person procedure — stripped of
anything Hagens-specific (the backlog, the gotchas log, current data-state
counts all stay in each individual repo's own CLAUDE.md, not the skill).
REUSABILITY GOAL RESTATED 2026-07-11, worth keeping visible: this methodology should generalize beyond Hagens (the original thinkr:/tgs: namespace split was specifically for this — see the "Reusable methodology" section near the top of this file), and it should stay HUMAN-IN-THE-LOOP — not a fully-automated pipeline. Every real verification step this session (search before trusting memory, check a hit is actually the right entity before linking it, distinguish "confirmed absent" from "not yet checked") was a human-guided judgment call, not something safe to fully automate.
FIRST-DRAFT "BOOTSTRAP A NEW PERSON" PROCEDURE, extracted 2026-07-11 from actually doing this for real (Nate Hagens himself — see persons.ttl, schools.ttl). Not yet a formal Skill, just the pattern worth generalizing once there's a second real example (ideally a genuinely different person/context) to test it against:
- Search to confirm real, verifiable identity and key affiliations — never from memory alone, this project's rule throughout.
- Check for a genuine Wikipedia page — verify it's actually THIS person, not a same-named different individual (hit exactly this trap today: the only Wikidata match for "Nate Hagens" was an unrelated ORCID researcher) or a non-canonical mirror site (EverybodyWiki is not Wikipedia, hit this today too).
- If real: get DBpedia URI (mirrors the Wikipedia title) AND separately verify the Wikidata QID directly — don't assume, check for disambiguation-page traps (the earlier Marcus Aurelius case).
- If NO real Wikipedia page exists: say so explicitly in the individual's own definition/comment, don't just silently omit the link — a future session needs to know "checked, confirmed absent" versus "nobody got around to checking yet," these are different states worth distinguishing (same epistemic-honesty principle as the Contributor/Mention OWA fix).
- Identify key affiliations (organizations, schools of thought) and verify EACH ONE'S own legitimacy before creating it as a School — don't inherit unverified trust from the person onto their affiliations.
- Add
memberOflinks connecting the Person to each verified affiliation. - Add credentials per the established scope (publications/credentials, not full employment history — see the social graph backlog item).
- Test: run a real query confirming every new link actually resolves before considering the bootstrap done — not just "the file parses."
- Never point a
tgs:LinkNote'stgs:confidenceattgs:ConfidenceType.Curatedwithout a human having actually reviewed the specific claim. See design decision #1 — this is the most important rule in the repo. - Concept/School definitions are paraphrases, not verbatim quotes — keep it that way (see design decision #6).
- Every
.ttlfile must parse individually and combined — runmake validatebefore considering an edit done. CI enforces this on push too, and additionally validates that every confidence value is one of the two real enumerated individuals, not just present. - Avoid bare DBpedia
dbr:prefixed names for people whose surface form ends in a period-adjacent token (e.g. "Jr.") — Turtle parses a trailing.as end-of-statement. Use the full<http://dbpedia.org/resource/...>IRI in those cases (seedata/seed/persons.ttl/schoolsofthought.ttlfor examples). data/generated/is the output ofextraction/promote_to_rdf.py— don't hand-edit it; edit the DuckDB staging rows and re-run the promote script.- Before any
git commit/git push, double check the terminal prompt actually saysnate-hagens-kg, notuwom-kg— MJSullivan has two projects open in separate terminal windows and has mixed them up before (harmless once, sincemainvsmastermakes it obvious after the fact, but worth avoiding).
Backward-looking, as opposed to the forward-looking Backlog above. Each of these actually happened and cost real debugging time; logged so a future session (or MJSullivan on a tired evening) doesn't repeat it.
-
owl:SymmetricPropertydoes NOT make the reverse direction queryable without a reasoner. Builtthinkr:convergesWithas formally symmetric and wrote a comment claiming "no need to assert both directions" — wrong. Plain SPARQL triple matching (what this project's tooling actually uses, no reasoner in the loop) only finds what's literally asserted. Caught by testing the reverse-direction query and getting zero results despite the forward triple being present. Fixed by adding the reverse triple explicitly and correcting the property's own comment. Any future symmetric property needs BOTH directions asserted as real triples, full stop — this is the same underlying lesson as always explicitly typingowl:NamedIndividualinstead of relying onrdfs:subClassOfinference, just a different OWL feature tripping the same wire. -
When splitting a graph by class, classes with ZERO instances yet are easy to forget entirely. During the 2026-07-11 one-class-one-file reorganization,
thinkr:Source(a real declared class, just with no individuals yet) fell through every categorization rule and printed as "uncategorized" — caught by checking the sanity-check output before trusting the triple-count match, not by the count itself (which would have been wrong too, but a raw mismatch alone doesn't tell you WHICH triples went missing).WorkandEpisodehad the same zero-instance status and were included correctly only because they were remembered from a previous session's backlog note — worth explicitly listing every declared class before any future repartition, not just the ones with visible data. -
A hardcoded file list anywhere is a latent bug waiting for the next reorganization.
promote_to_rdf.py'sload_existing_labels()hardcodedconcepts.ttl+people.ttlby name — broke silently (no crash, just returned incomplete/wrong matches) the momentpeople.ttlsplit intopersons.ttl+schools.ttl. Fixed by switching toglob(f"{seed_dir}/*.ttl"). Any future script that touches specific seed filenames by name should default to globbing instead, precisely because this project's file structure has already changed twice (2026-07-10 namespace split renamed files, 2026-07-11 governance reorganization split/renamed/dissolved several more) and will likely change again. -
rdflib 7.1.1 + Python 3.14 are incompatible (AttributeError at import time — a real upstream bug, not local misconfiguration).
python3 -m venv .venvon this Mac defaults to 3.14. Fixed viarequirements.txtpinningrdflib>=7.6.0. Ifmake venvever fails the same way again,pip install --upgrade rdflibinside the venv is the fix — don't re-diagnose. -
Turtle parses a trailing
.as end-of-statement, even inside what looks like a safe prefixed name. Bit us on DBpedia URIs for names ending "Jr." (Martin Luther King Jr., William R. Catton Jr.). Fix: use the full<http://dbpedia.org/resource/...>IRI in angle brackets for those cases instead of thedbr:prefixed form. -
g.parse()returns a truthy Graph object, sox.parse(f) or print(...)in a list comprehension NEVER calls print —orshort- circuits on the truthy left side. Caught in the originalmake validateone-liner; silently produced zero "OK" lines while still reporting a correct triple count, which almost passed as "working." -
A schema's formal
rdfs:domaincan silently drift from how a property is actually used.tgs:confidencewas declaredrdfs:domain tgs:Conceptfor a long stretch while every real usage was ontgs:LinkNote(laterthinkr:Evidence) — the property's ownrdfs:commenteven said "use this on the link, not the concept," contradicting its own domain axiom. Nothing caught this until a manual audit; SPARQL doesn't enforce domain/range at data-insert time, so wrong axioms don't error, they just quietly lie. -
Renaming by regex on class names is dangerous when individuals use a
Class.NameIRI convention. A pattern matching barePersonalso matches the START ofPerson.MarcusAurelius(nothing excluded a following.), silently reclassifying every domain individual as vocabulary during the thinkr:/tgs: split. Caught by running a live query and getting zero results, NOT by the file still parsing as valid Turtle — parsing successfully and querying correctly are different bars. -
A prefix rename has (at least) FOUR distinct token forms to catch, not one — learned the hard way across two separate rename passes (
tgs:→split, thenthinker:→thinkr:). (1) the RDF prefix token itself in.ttl/.sparqlfiles (thinker:Foo), (2) the PythonNamespaceidentifier (THINKER = Namespace(...)), (3) the namespace URI string (http://example.org/thinker#), and — the one that slipped through even after fixing the first three — (4) STRING-LITERAL prefix labels passed tograph.bind("thinker", THINKER). That fourth one is a lowercase string, invisible to a regex targeting the uppercase Python identifier, and produces genuinely confusing output if missed: a serialized file with@prefix thinker: <.../thinkr#>— label and URI silently mismatched. Caught only by actually runningpromote_to_rdf.pyend-to-end and reading its real output, not by any of the syntax/parse checks. Any future prefix rename needs to explicitly grep for all four forms, not assume fixing three means the job's done. -
Text-based find/replace across
.ttlfiles never touches vocabulary URIs embedded in Python code (TGS["ConfidenceType.Curated"]-style construction incompute_confidence.py,promote_to_rdf.py, and the CI YAML). Any future namespace/prefix change needs a SEPARATE, deliberate pass through every.pyand.ymlfile — it will not happen for free alongside the.ttlrename. -
A SPARQL query can "pass" a test while silently depending on leftover state from earlier in the same script/session —
query_examples.sparqlappeared correct after the first namespace fix because the test graph still hadthinkr:bound from an earlier unrelatedparse()call in the same Python process, masking that the file itself was missing its ownPREFIX thinkr:line. Would have failed immediately in the real Oxigraph CLI. Caught only by deliberately re-testing against a fresh graph with zero inherited bindings. Be suspicious of a first "pass" if the test setup could be leaking state. -
Two terminal tabs open in two different projects is a real, repeated risk, not a one-off.
main= this repo (nate-hagens-kg),master= the sibling project (folder renamedknowledge-graph->uwom-kgon 2026-07-10, branch name unchanged). Always glance at the prompt beforegit commit/git push. Already happened once — a commit with a Nate-Hagens-KG-themed message landed in the wrong repo's history (harmless, just confusing). -
Shell brace-expansion mistakes create silent literal folders — an early
mkdir -p {ontology,data/seed}-style command (missing proper expansion) created an actual folder named{ontology,datasitting empty alongside the real ones. Always worth afind . -maxdepth 1sanity check after any bulk directory-creation command. -
macOS Finder hides dotfiles/dotfolders by default (
.github,.gitignore,.venv), which can make a correctly-placed file look "missing." Cmd+Shift+. toggles visibility instantly.
make venv # create .venv and install requirements.txt
make validate # parse-check every .ttl file (expect 719 triples)
make load-oxigraph # load everything into a local Oxigraph store
make init-db # set up the DuckDB review-queue tables
make promote-dry # preview what promote_to_rdf.py would write
make promote # write approved staging rows into data/generated/
python scripts/compute_confidence.py --dry-run # preview calculatedConfidence
python scripts/compute_confidence.py # apply it — run after ANY Evidence editMJSullivan edits the TTL files directly rather than routing every addition
through a Claude session first, and reports changes back. A future
session should NOT assume full knowledge of current graph state from this
file alone — always check git log and re-run make validate before
making assumptions about what exists.
This repo is its own git repository and should stay that way — don't nest
it inside another project's directory or reference files outside this
repo's tree unless explicitly asked to. MJSullivan's other active project,
~/projects/uwom-kg (renamed from knowledge-graph on 2026-07-10), is
unrelated (different domain: units of measure / UWOM ontology) and uses
branch master, not main — a useful tell if you're ever unsure which
repo a terminal session is in.