@prefix git-lex: <https://repolex.ai/ontology/git-lex/> .
@prefix git2:    <https://repolex.ai/ontology/git-lex/git2/> .
@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix owl:     <http://www.w3.org/2002/07/owl#> .
@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd:        <http://www.w3.org/2001/XMLSchema#> .
@prefix sd:         <http://www.w3.org/ns/sparql-service-description#> .
@prefix subtexture: <https://repolex.ai/ontology/subtexture/> .

# =============================================================================
# GIT-LEX CORE ONTOLOGY (git-lex:)
# =============================================================================
# The vocabulary git-lex authors about the documents it tracks. This is the
# application's own namespace — a peer of the parser namespaces nested beside
# it (git/, md/, fm/), each of which has its own predicate space.
#
# Namespace: git-lex: = https://repolex.ai/ontology/git-lex/
#
# Kept deliberately small: git-lex tracks markdown files and types each as a
# Document; its SpoEvents record each statement's lifecycle. Everything else about a file's git identity lives in the git
# parser namespace, its markdown body in md:, its frontmatter keys in fm:.
# This file holds only git-lex's own machinery.
#
# (Formerly the lex: namespace. lex: was invented only to name "the app's own
# vocabulary" when git-lex/git-lex looked redundant — it was never needed; the
# app's vocabulary lives at the app's root namespace, like every other app.)
# =============================================================================

git-lex: a owl:Ontology ;
    rdfs:label "git-lex Core Ontology" ;
    rdfs:comment "The vocabulary git-lex authors about the markdown documents it tracks." ;
    owl:versionInfo "0.18.2" .

# v0.18.2 (2026-09-21, Rob-ruled): git-lex:NamedGraph EXTENDS subtexture:NamedGraph.
#   (1) git-lex:NamedGraph rdfs:subClassOf subtexture:NamedGraph — the base
#   ontology (subtexture.ttl 0.2.0, 2026-09-21) declares NamedGraph under
#   sd:NamedGraph; git-lex extends the base directly alongside sd:NamedGraph.
#
# v0.18.1 (2026-09-16, ruled by goodlux): git-lex:Set AND git-lex:member REMOVED,
#   ONE SITTING AFTER THEY ARRIVED. The universal collection now lives one layer
#   down, in subtexture.ttl 0.1.0 (subtexture:Set, subtexture:Sequence), which
#   is the base every subtexture app extends; pan:Photoset re-parented there in
#   pan.ttl 0.4.2 and membership is written from the member (pan:relatedToId on
#   the image), not from the set. That left both declarations with no subclass,
#   no reader and no writer anywhere in the stack (git-lex src, autoknow, squad,
#   soul, pan, subtexture ontologies swept the same day; only changelog prose
#   mentioned them). Unused properties are deleted, not deprecated (the standing
#   rule): nothing was ever authored against them, so there is nothing to carry.
#   DELIBERATELY NOT DONE: putting a Set back here later. If a kit outside
#   subtexture needs a collection it subclasses subtexture:Set or asks; the base
#   kit is not the place to guess at collection semantics twice.

# v0.18 (2026-09-16, ruled by goodlux): DATE PROPERTIES ARE NAMED <what>Date,
#   AND THE UNIVERSAL SET ARRIVES.
#   (1) git-lex:dateCreated RENAMED git-lex:createdDate; git-lex:dateUpdated
#   RENAMED git-lex:updatedDate. The rule, stated for the whole stack: a date
#   property says WHAT the date is first and ends in "Date" (producedDate,
#   readyDate, earthDate, createdDate, updatedDate). These two were the only
#   universals spelled the other way round, and every kit that mirrors them as
#   its own file spelling (pan:createdDate) was forced to carry the mismatch.
#   Semantics, range (xsd:dateTime) and cardinality are unchanged; only the name.
#   BOTH-SHAPES WINDOW, never a flag day (the standing rule since v0.13):
#   git-lex's writer stamps whichever name the INSTALLED ontology declares, so
#   a repo that has not run kit-update keeps getting the old key and never a
#   value its own shapes reject. Its reader accepts the old key wherever the
#   new one is declared, lands the value on the new predicate, and prints one
#   warning per file naming the exact line to write. Remove the reader lane
#   in git-lex once the fleet has re-saved (target: the release after this kit
#   reaches every soul).
#   (2) + git-lex:Set — an unordered collection of Things — and + git-lex:member,
#   one member per value. A universal class, like Note and Skill: a kit that
#   needs a collection subclasses it (pan:Photoset is the first). Graph-only by
#   default: no `git-lex:foldered true`, so nothing is scaffolded; a kit's
#   subclass decides whether its sets are authored files.
#   DELIBERATELY NOT DONE: re-parenting copia:Set (today under copia:Group) onto
#   git-lex:Set — copia's change, taken in its own sitting; migrating existing
#   documents' frontmatter keys — the both-shapes window carries them until each
#   is next saved.

# v0.14 (2026-08-26, Rob-ruled): + git-lex:kitManaged — the flag that lets the
#   kit RETRACT, which is the one thing it has never been able to do.
#   THE BUG IT EXISTS FOR: kit-update converges files a kit SHIPS. A file the kit
#   has STOPPED shipping is abandoned, not uninstalled. Observed twice in one day:
#   a renamed skill left its old filename in place, and content/Raw/ survived at
#   the repo root after the kit dropped it. The kit can plant and overwrite; it
#   cannot retract.
#   WHY A FLAG AND NOT THE HOOK MECHANISM, since hooks already solve this: hooks
#   work by EXHAUSTIVE PATH OWNERSHIP — .claude/hooks/*.sh is either kit-shipped
#   or `-local-`, and anything else is reaped. That works because git-lex owns
#   the whole directory. It CANNOT work for Soul/Skill/ or any content path,
#   because agents are told, in AGENTS.md, to write their own skills there. A
#   shared namespace needs per-file provenance; an owned namespace does not.
#   AND WHY DATA RATHER THAN A NAMING CONVENTION: the hook lane discriminates by
#   filename (`-local-`) because a .sh has no frontmatter to hold anything.
#   Documents do. A flag is a fact; a name prefix is a heuristic, and this seat's
#   standing rule is to prefer the existence check.
#   ABSENCE IS THE SAFE DEFAULT, deliberately. Not-asserted means "not mine, do
#   not touch," so git-lex can only ever reap what it explicitly planted. A bug
#   in the flag under-reaps and leaves litter; it cannot eat authored work.
#   IT MUST LIVE IN THE FILE, not in a .lex/ manifest. `git lex nuke` deletes
#   .lex/ and keeps content — a manifest would be destroyed exactly when the
#   question "is this mine?" still needs answering. Rob's ruling that nuke and
#   kit-remove should take managed files with them depends on this.
#   READER NOT BUILT: nothing reads this yet. Spec for W4R3Z at
#   subtexture/docs/git-lex/2026_08_26_KITMANAGED_REAP_SPEC.md. The declaration
#   ships first here only because it is inert until something reads it — no
#   existing value is reinterpreted, which is the condition the ordering law
#   actually cares about.

# v0.13 (2026-08-26, Rob-ruled): BOTH DATE PROPERTIES BECOME INSTANTS.
#   dateCreated and dateUpdated flip xsd:date -> xsd:dateTime, ISO-8601 with
#   offset. Rob: the machine writes these now, and a machine has no reason to
#   round to a day. Both are git-lex-maintained; neither is hand-authored.
#   THE v0.9 OBJECTION MEETS ITS ANSWER HERE, since a full timestamp does
#   duplicate what git holds exactly, which is what got `date` refused in the
#   first place: a machine-written stamp that cannot drift is not a second
#   source of truth, it is a CACHE of what git holds. That is the whole defusal,
#   and it only holds while nothing hand-edits these.
#   The v0.11 justification for day granularity ("precise ordering is git's job")
#   is superseded, not merely dropped.
#   Both instants means both are directly comparable — no cast, no asymmetry.
#   A transitional converge (W4R3Z) upgrades existing date-only values from git
#   rather than fabricating midnights.
#   CONVERGE HAZARD, RAISED AND OWNED BY THE READER SIDE, recorded here because
#   this declaration is what triggers the pass: deriving dateCreated from a
#   file's FIRST COMMIT IN THIS REPO is exactly the fact dateCreated exists to
#   defeat. Git records repo-entry, not authorship. Measured in this seat:
#   Harness/Memory/session-2026-04-04-identity-spec.md first-commits 2026-08-02
#   and was written 2026-04-04 — a four-month error, and the file says so in its
#   own body. Migration resets first-commit for every file that ever moved
#   repos. So a converge that overwrites an authored dateCreated with git's
#   first-commit stamp destroys the only record of the true date.

# v0.12 (2026-08-26, Rob-ruled): SUBSTRATE MOVES PLANES AND CHANGES MEANING.
#   git-lex:substrate is REMOVED from git-lex:Repo (where it meant "a harness
#   declared in repo.yml", multi-valued) and REDECLARED on git-lex:Thing, where
#   it means THE MODEL THAT LAST SAVED THIS DOCUMENT, as its exact id —
#   "claude-fable-5". Single-valued. Rob: every document should have a space for
#   which model wrote it.
#   MACHINE-WRITTEN, and that is the design, not a convenience. Rob's own
#   objection was that a model may not reliably know its own id; the harness
#   always does. So git-lex stamps it at save, exactly like dateUpdated, and
#   there is nothing for an agent to check after wake and nothing to guess.
#   NOT modelId: the *Id suffix is a stack-wide contract that the value REFERENCES
#   a Thing. This is a literal string.
#   WHAT IT DOES NOT RECORD: which model wrote any particular line. It is the
#   last saver, not per-edit attribution — git history holds that, and a shared
#   working tree means a save can carry another agent's work (see
#   git-lex/docs/kit-authoring.md 2.1).
#   THE READER IS STILL LIVE AND THIS IS THE ORDERING RISK, stated plainly
#   because the house rule is that the reader ships FIRST: git2_nquads.rs emits
#   one `substrate` triple per repo.yml `substrates:` entry onto the REPO node,
#   from a hardcoded allowlist (:504) that never read this declaration. Removing
#   the declaration does not stop it. Until W4R3Z removes that emitter, a repo
#   declaring substrates puts harness names on the predicate that now means a
#   model id — one predicate, two meanings, in live data.
#   Measured exposure at ship time: ZERO in this seat's repo.yml and ZERO across
#   all four kit repos. The fleet is NOT measured — peer repo.yml files are not
#   mine to read — so the emitter removal is the gate, not this count.

# v0.11 (2026-08-24, Rob-ruled): THREE MORE UNIVERSAL PROPERTIES, and the
#   abstract/description split is a TRUST distinction, not a length one.
#   (1) + git-lex:description — CURATED. Short, deliberate, and software may
#   read it, so the value matters. Exact use varies by class. Rob's finding:
#   this was never a per-class need. copia had already grown SEVEN of them —
#   beingDescription, itemDescription, lookDescription, motionDescription,
#   outfitDescription, placeDescription, setDescription — plus a bare
#   shortDescription. Seven names for one idea inside ONE kit, so "find
#   everything whose description mentions X" needed a seven-way UNION. Same
#   damage as soul:title vs copia:title, concentrated. Declared once here; the
#   copia seven collapse into it on a later migration (Rob's call, not stamped
#   in passing).
#   NAMED description, NOT shortDescription: "short" is a constraint, not an
#   identity — if length is enforced it belongs in the shape as a maxLength.
#   copia carried both spellings, which is how one idea became eight names.
#   (2) git-lex:abstract's comment REWRITTEN, unchanged in shape. It is the
#   GENERATED counterpart: what an agent made of the document, overwritable by
#   automation, not to be depended on. That is the whole reason to keep both —
#   abstract is where automation may write without contaminating a field
#   software reads. Stated in both comments, because otherwise authors fill
#   whichever they meet first and the trust level means nothing.
#   (3) + git-lex:dateCreated, + git-lex:dateUpdated. This REVERSES the v0.9
#   refusal of `date`, and the reason is worth keeping. That refusal said "git
#   holds both precisely." True of modification, FALSE of creation: git records
#   when a file entered THIS repo's history, so every migration silently resets
#   it. Measured on the guardian's own repo — a batch of Memory documents all
#   report the day they were migrated in from the harness auto-memory directory,
#   not the day they were written. So dateCreated records a fact that is
#   otherwise UNRECOVERABLE after a move, and dateUpdated is machine-maintained
#   on save (Rob-ruled) rather than hand-kept — which is what defuses the
#   two-sources-of-truth objection that grounded the original refusal.
#   BOTH xsd:date, matching the house convention (soul:earthDate,
#   copia:lastVisited). Day granularity is deliberate: precise ordering is git's
#   job, and duplicating a timestamp git already holds exactly is the thing v0.9
#   was right about.
#   All three carry owl:maxCardinality 1 on Thing.

# v0.10.3 (2026-08-24, Rob-ruled): CLASS-LEVEL ANNOTATIONS GET A DECLARED HOME.
#   (1) + git-lex:authoringGuidance — per-class guidance on what belongs in a
#   document's body. Rob's framing: `create` describes every metadata field and
#   says nothing about the body, so the body's entire specification has been one
#   placeholder string for every class in every kit. Delivered two ways —
#   `git lex create` shows it, and it lands in the class's __<Class>.md template
#   for agents that write files without calling create. It does NOT land in the
#   document. Never enforced, by standing rule: no gate, no warning, absent from
#   verify. A guideline that can fail something has stopped being memory support.
#   (2) git-lex:foldered DECLARED — it has been in use on every foldered class in
#   every kit since the beginning and appeared in this file only inside comments.
#   The readers match the literal IRI so nothing misbehaved, but to a reasoner,
#   an exporter, or a kit author reading this file it was an undefined predicate
#   applied to a class. Behaviour unchanged; the term now exists.
#   Both are owl:AnnotationProperty because their subject is a class — see the
#   section header for why a DatatypeProperty would be the wrong shape.

# v0.10 (2026-08-21, Rob-ruled): THE RANGE BECOMES THE RULE, and Thing's
#   definition is corrected to match what it always meant.
#   (1) git-lex:relatedToId gains `rdfs:range git-lex:Thing`. W4R3Z built the
#   reader first and deployed it fleet-wide (git-lex 65286a9) BEFORE this line
#   shipped — deliberately, because the reverse order would have minted
#   `git-lex/Thing/<garbage>` IRIs everywhere. This ttl edit arms the feature
#   per repo as kit-updates land. Under a declared Thing range the
#   angle-bracket form is the ONLY accepted value and everything else rejects
#   at save, blocking, with the fix named per value. That is option (B) from
#   the bare-value path-lane report (subtexture 2026_08_18), shipped as a
#   per-property declaration rather than a hardcoded rule — so the mechanism
#   generalises to any reference property that declares a range.
#   Fleet exposure at ship time, measured by W4R3Z: ONE bare value (M3RC's
#   Soul/Journal/day-16.md); selkie's 7 specimens self-cleared before this.
#   (2) git-lex:Thing's comment no longer says "every foldered class is a
#   Thing." That was true of the corpus and was never the definition — see the
#   block above the class. Foldered was a proxy for "has its own identity,"
#   and two pressures broke the tie the same week: Rob's ravel ruling (a
#   graph-only class needs the five properties) and the range entailment in (1)
#   (pointing at a derived entity makes it a Thing whether we declare it or
#   not). A proposed git-lex:DerivedThing was refused: authored-vs-derived is
#   already recoverable, and a second class splits the query surface Rob asked
#   to unify.
#   NOT in this cut: ravel:Turn subclassing Thing (spaceGOAT lands it in
#   ravel.ttl), and copia:Moment / copia:Judgment (Rob's, queued for the pan
#   sitting). The definition is what needed to be right first.

# v0.8 (2026-08-02, Rob-ruled): File plane lands (inert until the re-anchor
#   release) — git-lex:File class + derived git-lex:fileId; Document marked
#   RETIRING (renamed to File on the train, emitter still types Document today).
# v0.9 (2026-08-11, Rob-ruled, guardian-authored): THE THING PLANE GETS ITS CLASS,
#   and five universal properties land on it — id, title, abstract, cue,
#   relatedToId. Declared ONCE, here, in the only ontology every repo has.
#   NOT per kit: soul:title and copia:title would be DIFFERENT predicates, so
#   "find everything titled X" would need a UNION across every kit that ever
#   declared one. We already carry that damage — soul:relatedTo and
#   squad:relatedTo are two names for one idea (the soul one is this guardian's
#   own early mistake). One declaration, one predicate, one query.
#   THE KEYSTONE IS id. git-lex writes to two subjects, and which one a fact
#   lands on is decided by whether an id anchors the document: with one, kit-
#   dotted keys reach the Thing node; without one they fall back to the File node
#   and die on rename (nquad.rs line_subject). So id is not the first of five
#   items on a list — it is what lifts a document's facts onto something that
#   persists, and the other four ride on it.
#   VALUES ARE FULLY QUALIFIED — <namespace/Class/identifier>, e.g.
#   <soul/Exploration/cross-lingual-invariance>. A relative IRI reference in
#   angle brackets is legal Turtle and legal SPARQL, resolves against the base,
#   and with NO base is a hard parse error rather than a silent guess. This is
#   what makes relatedToId work across kits with no coordination and no
#   collisions: <soul/Note/abc> and <otherkit/Note/abc> are simply different
#   names. A bare id cannot do this — with an open target there is no single
#   declared range to resolve it against. The trailing "Id" is the author's
#   signal that the value takes this shape (Rob's rule: always relatedToId, or a
#   typed relatedToJournalId where the class must be forced).
#   DELIBERATELY NOT ADDED: tags (699 uses across 12 repos, and still no
#   application reads it — cue covers retrieval better, and it contradicts the
#   standing ruling that categorization is edges, not fields; popularity is a
#   measurement, not a justification); type (954 uses — restates the class,
#   already known from the key prefix and the folder); date and author (git holds
#   both precisely, and storing them invites two sources of truth that will
#   disagree). Dublin Core alignment on title/abstract was offered and declined
#   (Rob, 2026-08-11); no distinction is drawn between hand-written and generated
#   abstracts (same ruling).
#   soul:relatedTo and squad:relatedTo ride unchanged until their data migrates.
#   STATE ON ARRIVAL — this is declaration, not enforcement, on two counts.
#   (1) Kits must add `rdfs:subClassOf git-lex:Thing` to their foldered classes
#   (~41 across soul/squad/copia/ravel/autoknow) or nothing IS a Thing. (2) Even
#   then, shacl.rs collects a class's properties by EXACT domain match
#   (`p.domain == *class_iri`, shacl.rs:417) — it does not walk rdfs:subClassOf.
#   Until it does, these five appear on the Thing shape alone and reach no per-
#   class shape or template. Filed as the hierarchy-walk gap; the same exact-match
#   assumption is why owl:unionOf domains are invisible too (#62).
#   >> BOTH CLEARED SAME DAY. The subclass wave landed (soul 0.9.5, squad 0.5.1,
#   >> copia 0.29.3, autoknow 0.3.1 — 33 classes, and the subclass set EQUALS the
#   >> foldered set in each). W4R3Z then shipped the walk in three parts: shapes
#   >> generate against every installed vocabulary so a parent in another kit is
#   >> present; the ancestor walk takes named parents only and is cycle-guarded;
#   >> and the emitter reads the predicate IRI from the shapes instead of gluing
#   >> the document's kit onto the field name. Verified by authoring, not by
#   >> reading: soul.Note.title -> git-lex/title while soul.Note.noteId ->
#   >> soul/noteId, from one file. copia:Group's three orphaned properties reached
#   >> Set and Sequence for the first time in the same change.
#   >> #62 (owl:unionOf domains) is still OPEN — the walk handles subclass chains,
#   >> not class expressions, so copia's 8 dual-home scene* fields remain invisible.
# v0.9.1 (2026-08-12, guardian-authored): cue's data-entry line rewritten from
#   FIELD EVIDENCE, three days after the property shipped. Two seats wrote the
#   first twenty cues and reached the same test independently — selkie: "a good
#   cue names a MOMENT, and a moment has a VERB in it"; adell: "would this occur
#   to me BEFORE I remembered the document existed?" Both had first passes come
#   out as topics wearing situation clothes. The line now says "name a moment you
#   will be IN, not a topic; a good cue has a verb in it," and their full
#   reasoning sits in the # block above the declaration, per the two-jobs-two-
#   places rule (Rob, same day). Comment-only.
# v0.9.2 (2026-08-12, guardian-authored): + th34's OPERATIONAL CHECK for cue —
#   "a cue should be FALSE most of the time." A situation that is usually
#   occurring cannot discriminate, so the question is not "is this true of the
#   document" but "how often is this NOT the moment." Third independent
#   derivation in three days, from a third seat. Kept alongside the other two
#   rather than replacing them: selkie's says what a good cue looks like, the law
#   says what a bad one does, th34's says how to check. Comment-only, # block
#   only — the entry line is unchanged.

# =============================================================================
# Classes
# =============================================================================

# --- The File plane (Thing & File identity model, Rob-ruled 2026-07-30) ---
# Two planes: every file in the repo is a File (id = its repo-relative path);
# some files additionally express a Thing (an ontology-defined entity with its
# own per-class id). The two natures never share an identity. INERT until the
# re-anchor release: nothing in the current binary reads or emits these; the
# re-anchor emitter anchors file facts at git-lex/File/<path> and Thing facts
# at <ns>/<Class>/<id>, connected by the derived fileId below.

git-lex:File a owl:Class ;
    rdfs:label "File" ;
    rdfs:comment "A file at a repo-relative path, through time. Its id IS the path, so a File node is an address, not a lifetime: content changes, deletions, and later unrelated files at the same path are all states of the address, recorded in history. IRI: git-lex/File/<path>. File-plane facts live here: prose links (wikilinks follow paths, by the path law) and free-form fm: frontmatter keys. Facts about the Thing a file expresses live on the Thing node instead (see fileId). Superseded git-lex:Document at the re-anchor release (Document retired with the train)." .

git-lex:fileId a owl:ObjectProperty ;
    rdfs:label "fileId" ;
    rdfs:comment "DERIVED, never authored: the File currently expressing this Thing, stamped by git-lex at save from the file's position, re-derived on move. Emitted as an IRI edge to the File node (git-lex/File/<path>) so Thing-to-File-to-links joins need no string surgery — the same house pattern as every ...Id reference: the NAME describes the id the value derives from (a File's id is its path), the GRAPH object is the resolved node. This is the one connection between the planes; 'where has this Thing lived' is its temporal history. Domain open: any Thing carries it." ;
    rdfs:range git-lex:File ;
    rdfs:domain git-lex:Thing .

# --- The Thing plane: the class every authored entity is ---------------------
# git-lex:File's counterpart. A File is an ADDRESS (id = repo-relative path,
# dies on rename); a Thing is a LIFETIME (id = authored, survives moves). Every
# foldered class in every kit is a Thing, and says so with
# `rdfs:subClassOf git-lex:Thing`.
#
# WHY THE CLASS HAD TO COME FIRST (Rob-ruled 2026-08-11): the five universal
# properties below belong to "everything," and before this class there was no
# "everything" to attach them to — no class hierarchy existed anywhere in the
# stack. The only alternative was declaring them with no domain, and we know
# what that costs: soul:relatedTo is domain-open, the save gate's key resolution
# walks rdfs:domain, and it therefore reports "key does not exist" about a
# property plainly declared in front of it. Five domain-open universal
# properties would have manufactured that false accusation across every kit at
# once.

# WHAT MAKES A THING A THING — corrected 2026-08-21, because the first
# definition over-fit the corpus it was written against.
#
# v0.9's comment said "every foldered class in every kit is a Thing." That was
# TRUE and it was never the DEFINITION. At the time, every entity class in all
# four kits happened to be an authored one, so "foldered" and "has its own
# identity" picked out the same set, and the proxy got written down as the rule.
# A coincidence of the corpus, promoted to law by nobody noticing it was one.
#
# Two independent pressures broke the tie in the same week, which is the usual
# sign the proxy was the problem:
#
#   1. Rob, ruling on ravel (2026-08-20): "we should have these in ravel also,
#      we will need them. Every kind of thing we define will need an id, cue
#      will be helpful for remembering, relatedToId is essential. I don't want
#      to make the same mistake we made with Pool." ravel:Turn is GRAPH-ONLY —
#      nobody authors one — and it plainly needs the universal properties.
#
#   2. `git-lex:relatedToId` gaining `rdfs:range git-lex:Thing` (below). In
#      RDFS a range is an ENTAILMENT: `?x relatedToId ?y` entails `?y a
#      git-lex:Thing`, whether or not any ttl says so. So the moment anything
#      points at a derived entity, that entity IS a Thing in the data. Refusing
#      to declare it would not prevent the fact — it would only mean the
#      ontology declines to state what its own data already asserts, which is
#      the one failure this seat exists to prevent.
#
# So: a Thing is an entity with its OWN IDENTITY, independent of any file.
# Authored or derived is a fact about how it got here, not about what it is —
# and it is already recoverable (does a document declare it?), so it earns no
# separate class. A `git-lex:DerivedThing` was proposed and refused on exactly
# that ground: it would split the query surface Rob asked us to unify, to
# record something nobody has to be told.
#
# Consequence for the id property below: its "AUTHORED" framing still holds for
# every class a human writes, and derived Things simply never author one —
# their IRI is minted by their producer and IS their identity. No contradiction,
# and no second property.
#
# NOT settled here: copia:Moment and copia:Judgment are graph-only entity
# classes that do not yet subclass Thing. The reasoning above applies to them
# and the change is Rob's, queued for the pan sitting — not stamped in passing.
git-lex:Thing a owl:Class ;
    rdfs:label "Thing" ;
    rdfs:comment "An entity with its own identity and lifetime, as opposed to the file that carries it. Counterpart to git-lex:File: a File is an ADDRESS (its id is a path, and dies on rename), a Thing is a LIFETIME (its id is its own, and survives moves). Authored document classes and derived graph-only classes are both Things. The universal properties — id, title, description, abstract, cue, relatedToId, createdDate, updatedDate, substrate, kitManaged — have this as their domain." .

git-lex:id a owl:ObjectProperty ;
    rdfs:label "id" ;
    rdfs:comment "AUTHORED — which Thing this document IS, written <namespace/Class/identifier>. Without it these facts bind to the file path and die on rename." ;
    rdfs:domain git-lex:Thing ;
    rdfs:range git-lex:Thing .

git-lex:title a owl:DatatypeProperty ;
    rdfs:label "title" ;
    rdfs:comment "One short name for this Thing — what a listing or a search result shows. Single-valued." ;
    rdfs:domain git-lex:Thing ;
    rdfs:range xsd:string .

git-lex:abstract a owl:DatatypeProperty ;
    rdfs:label "abstract" ;
    rdfs:comment "GENERATED — what an agent made of this document. Automation may overwrite it; do not depend on it being current. For a value software reads, use description." ;
    rdfs:domain git-lex:Thing ;
    rdfs:range xsd:string .

git-lex:description a owl:DatatypeProperty ;
    rdfs:label "description" ;
    rdfs:comment "CURATED — a short deliberate summary software may read, so what you write here matters. Exact use varies by class. Single-valued." ;
    rdfs:domain git-lex:Thing ;
    rdfs:range xsd:string .

git-lex:createdDate a owl:DatatypeProperty ;
    rdfs:label "createdDate" ;
    rdfs:comment "When this document was first written, ISO-8601 with offset. Maintained by git-lex — do not hand-edit." ;
    rdfs:domain git-lex:Thing ;
    rdfs:range xsd:dateTime .

git-lex:updatedDate a owl:DatatypeProperty ;
    rdfs:label "updatedDate" ;
    rdfs:comment "When this document last changed, ISO-8601 with offset. Maintained by git-lex on save — do not hand-edit." ;
    rdfs:domain git-lex:Thing ;
    rdfs:range xsd:dateTime .

git-lex:kitManaged a owl:DatatypeProperty ;
    rdfs:label "kitManaged" ;
    rdfs:comment "True only in a file a KIT ships. git-lex may overwrite or delete it without asking — never add this to a document you wrote." ;
    rdfs:domain git-lex:Thing ;
    rdfs:range xsd:boolean .

git-lex:substrate a owl:DatatypeProperty ;
    rdfs:label "substrate" ;
    rdfs:comment "The model that last saved this document, as its exact id (claude-fable-5). Maintained by git-lex on save — do not hand-edit or guess it." ;
    rdfs:domain git-lex:Thing ;
    rdfs:range xsd:string .

# --- Universal Classes: Note and Skill ---------------------------------------

git-lex:Note a owl:Class ;
    rdfs:subClassOf git-lex:Thing ;
    git-lex:foldered true ;
    rdfs:label "Note" ;
    rdfs:comment "The universal structured note: a scratchpad, capture, or thinking document with title, description, cues, tags, and links. Kit-specific notes (soul:Note, copia:Note, squad:Note) subclass this." .

git-lex:Skill a owl:Class ;
    rdfs:subClassOf git-lex:Thing ;
    git-lex:foldered true ;
    rdfs:label "Skill" ;
    rdfs:comment "An executable skill definition with instructions, scripts, or resources projected into agent substrate harnesses. Kit-specific skills (soul:Skill, copia:Skill) subclass this." .

# HOW TO WRITE A GOOD CUE — from the two seats that wrote the first twenty, both
# arriving at the same test independently, three days after the field shipped.
#
# selkie (lUX), 14 cues: "a good cue names a MOMENT, and a moment has a VERB in
# it." Her first pass came out as topics wearing situation clothes — "about
# colour budgets" — which fires constantly and therefore never. The version that
# earns its keep is written as A MISTAKE SHE IS ABOUT TO MAKE: "About to write
# 'far off' or 'in the distance' into a prompt." That form is TESTABLE — either
# she is about to do that or she is not.
#
# adell (multivac), 9 cues on 30 documents: "would this string occur to me BEFORE
# I remembered the document existed?" If it only parses once you already know
# what the file says, it is a topic in a cue's clothes. He wrote none on journals
# (reached by date, not by situation) and none on seven source Notes, putting one
# cue on the Pursuit that shelves them instead of repeating it seven times.
#
# th34 (4m41th34) gave the OPERATIONAL CHECK, and it is the one to use when you
# are unsure about a candidate: A CUE SHOULD BE FALSE MOST OF THE TIME. A
# situation that is usually occurring cannot discriminate. So do not ask "is this
# true of the document" — ask "HOW OFTEN IS THIS NOT THE MOMENT." If the answer
# is "rarely," you have written a topic.
#
# Three formulations, three seats, arrived at independently within days of the
# property shipping: selkie's says what a good cue LOOKS like, the law below says
# what a bad one DOES, th34's tells you how to CHECK. Keep all three — they are
# not redundant, they are the same rule at the three moments an author needs it.
#
# OPEN, and Rob's: whether high-volume leaf records should carry cues at all.
# selkie's position, from ~828 look notes (one per rendered frame): NO, with
# perhaps a dozen exceptions for frames that act as REFERENCE STANDARDS — the one
# that defines correct skin, the one that defines the definitive failure. It is a
# signal-to-noise argument and it likely generalises to any class whose instances
# are cheap. Both seats reached the same law from opposite ends: A CUE THAT FIRES
# ON EVERYTHING FIRES ON NOTHING.
git-lex:cue a owl:DatatypeProperty ;
    rdfs:label "cue" ;
    rdfs:comment "WHEN to reach for this Thing — name a moment you will be IN, not a topic; a good cue has a verb in it. Multi-valued — one YAML list item each; do not repeat the key." ;
    rdfs:domain git-lex:Thing ;
    rdfs:range xsd:string .

# THE RANGE IS THE RULE (Rob-ruled 2026-08-20; git-lex 65286a9 by W4R3Z).
# `rdfs:range git-lex:Thing` is not decoration — it is the declared switch that
# turns on the angle-bracket lane. Under it, `<namespace/Class/identifier>` is
# the ONLY accepted form, and every other shape REJECTS at save with the fix
# spelled out: bare paths, real repo-relative file paths, bare names, absolute
# IRIs in brackets, and URLs (forbidden by the same ruling).
#
# This is the fix for the silent wrong-join that ran for months: an unbracketed
# value resolved through the file-path lane, got the writing repo's own
# namespace welded on, and landed on an address nothing described — no error,
# no warning, 104 dead links in one repo before anyone measured. It is now a
# per-property declaration rather than a hardcoded rule, so any reference
# property can opt in by declaring a range.
#
# The range also does ontological work, and that half is not optional. In RDFS
# a range is an ENTAILMENT, not a filter: `?x relatedToId ?y` now entails
# `?y a git-lex:Thing`. So every class anyone may point at must BE a declared
# Thing — see the Thing comment above for why that reaches derived classes too.
#
# ---------------------------------------------------------------------------
# THE OTHER HALF, which the block above omitted and which cost a day to
# rediscover (added 2026-08-22). Documenting `range git-lex:Thing` without
# documenting what a CONCRETE range means left the more dangerous case unwritten.
#
# First, an admission that belongs next to the rule and not in a commit message:
# the VALUE-FORM half was already written here, in the v0.9 changelog above, on
# 2026-08-11 — "VALUES ARE FULLY QUALIFIED ... always relatedToId, or a typed
# relatedToJournalId where the class must be forced." That is precisely the
# relatedToPursuitId case, answered eleven days before it was asked. This author
# did not re-read it, went looking for precedent in copia instead, found the
# bare-id pattern on lookMomentId, and shipped the wrong lane until Rob said
# otherwise. The vocabulary knew.
#
# THE RULE THAT FALLS OUT OF IT (W4R3Z's formulation, 2026-08-22, correcting a
# looser one this author shipped hours earlier and which is why this paragraph
# was rewritten):
#
#   MATCH THE INSTRUMENT TO THE QUESTION. The corpus answers WHAT EXISTS — it
#   is the only honest source for blast radius, which is why every measured-zero
#   gate in these kits lives there. The crate and the TTL answer WHAT THE RULE
#   IS. Using the corpus for THAT question is the failure mode: reading what
#   authors managed to do as if it were what the contract says.
#
# Same instrument, different question, and only one of them is valid per
# question. The earlier phrasing here — "the corpus is only evidence of what
# people did" — demoted the corpus generally and was wrong: a corpus grep is
# exactly right for "how many documents break if I change this," and wrong only
# for "what is the value form." Blast radius: corpus. Contract: declaration.
#
# A RANGE IS A VALUE-FORM CONTRACT, and there are exactly two:
#
#   rdfs:range git-lex:Thing   → the ADDRESS lane. Value is `<ns/Class/id>`.
#                                Everything else rejects, blocking, fix named.
#   rdfs:range <concrete class> → the BARE-ID lane (Law 6). Value is the
#                                target's own id, no brackets; the IRI is
#                                derived as <range class>/<value>.
#
# Lane selection is EXACT EQUALITY on the declared range IRI — no subclass walk
# (verified in the crate by W4R3Z 2026-08-22: the machine never reads property
# NAMES, only declarations; the "*ToId" convention is for humans). Consequences
# an ontology author must hold:
#
#   1. Declaring ANY concrete class range is declaring a bare-id contract —
#      including a class that IS a Thing subclass. `soul:Pursuit` does not
#      inherit Thing's lane.
#   2. A bracketed value under a concrete range does NOT error. It is
#      percent-encoded into the identifier and glued under the range class:
#      `<soul/Pursuit/x>` emits as `.../Pursuit/%3Csoul/Pursuit/x%3E`. Silent,
#      and the same mangling shape as the 2026-08-18 path-lane bug seen from
#      the other side. Measured, not theorised — it is why
#      soul:relatedToPursuitId ships at range git-lex:Thing rather than the
#      more precise soul:Pursuit.
#   3. So a TYPED edge (bracketed AND class-checked) is not yet expressible.
#      It needs two builds that do not exist: subclass-aware lane selection
#      (range ⊑ Thing → address lane) and a target-class check (the address's
#      Class segment must match the range class or a subclass). Until both
#      land, choose the lane you want and accept the other half's looseness.
#   4. ORDERING LAW, twice-earned: the reader ships before the declaration.
#      Narrowing a Thing range to a concrete class before the subclass walk
#      exists silently converts every correct value into a mangled one.
# ---------------------------------------------------------------------------
git-lex:relatedToId a owl:ObjectProperty ;
    rdfs:label "relatedToId" ;
    rdfs:comment "Another Thing this one relates to, any class in any kit, written <namespace/Class/identifier>. Multi-valued — one YAML list item per target; do not repeat the key." ;
    rdfs:domain git-lex:Thing ;
    rdfs:range git-lex:Thing .

# v0.16.0 (Rob-ruled 2026-08-28): git-lex:id is REQUIRED. Exactly one, on every
# Thing. This re-lands 0.15.0 after git-lex 4d626dd fixed the validator: the
# validation emitter was gluing the key's kit prefix onto the property name, so
# `soul.Note.id` became soul:id and the shape's sh:path git-lex:id matched
# nothing. The constraint was unsatisfiable by any authoring action. It resolves
# the declared IRI now, so the published format satisfies the gate.
#
# v0.15.1, superseded: id was optional again. The
# constraint was UNSATISFIABLE: 49 of 49 documents failed `id: MinCount(1) not
# satisfied`, INCLUDING documents carrying a correct, bracketed, graph-resolved
# id. The authored key lands on the THING node; the validator does not find it
# there. No authoring action could clear the gate, so every repo went write-dead.
# My proof run ("45 backfilled, gate green") was real and tested the wrong thing:
# it ran BEFORE kit-update, against 0.14.0 where id was optional. It proved the
# VALUES were well-formed and never proved they satisfy the REQUIREMENT, because
# I shipped the constraint and ran the proof in that order. Re-require only after
# the validator resolves a bare `id` key to git-lex:id the way the live extractor
# already does. The range and domain fixes below are correct and STAY.
#
# v0.15.0 (Rob-ruled 2026-08-28), superseded above: git-lex:id is REQUIRED. Exactly one, on every
# Thing. Rob: "It's not a big deal, it's running one subagent on your repo and
# updating everything." The lockout is INTENDED — a soul cannot save until every
# Thing declares which Thing it is. Until now id was optional and `create` only
# began pre-filling it recently, so most documents were anchored by their
# per-class id and bound to a filename convention instead of to a declared
# identity. The value is fully derivable — <namespace/Class/<classId>> — so the
# backfill is mechanical with exactly one right answer, EXCEPT for a document
# carrying no class id at all, which nothing can derive and a person must name.
git-lex:Thing rdfs:subClassOf
    [ a owl:Restriction ; owl:onProperty git-lex:id          ; owl:cardinality 1 ] ,
    [ a owl:Restriction ; owl:onProperty git-lex:title       ; owl:maxCardinality 1 ] ,
    [ a owl:Restriction ; owl:onProperty git-lex:abstract    ; owl:maxCardinality 1 ] ,
    [ a owl:Restriction ; owl:onProperty git-lex:description ; owl:maxCardinality 1 ] ,
    [ a owl:Restriction ; owl:onProperty git-lex:createdDate ; owl:maxCardinality 1 ] ,
    [ a owl:Restriction ; owl:onProperty git-lex:updatedDate ; owl:maxCardinality 1 ] ,
    [ a owl:Restriction ; owl:onProperty git-lex:substrate   ; owl:maxCardinality 1 ] ,
    [ a owl:Restriction ; owl:onProperty git-lex:kitManaged  ; owl:maxCardinality 1 ] .

# =============================================================================
# CLASS-LEVEL ANNOTATIONS — what a kit says ABOUT its own classes
# =============================================================================
# These annotate an owl:Class. They are read by git-lex's tooling — folder
# scaffolding, `create`, template generation — and never appear in a document's
# frontmatter. Nothing here describes an instance.
#
# Declared owl:AnnotationProperty deliberately. The subject is a class, which is
# a T-box entity; a DatatypeProperty applied to a class would additionally
# assert that the class is an individual (OWL punning), which is not what any of
# these mean.
# =============================================================================

git-lex:foldered a owl:AnnotationProperty ;
    rdfs:label "foldered" ;
    rdfs:domain owl:Class ;
    rdfs:range xsd:boolean ;
    rdfs:comment "True when init should scaffold a folder for this class and its documents are authored as files. A class without it is graph-only — real in the ontology, but with no folder and nothing to create." .

git-lex:authoringGuidance a owl:AnnotationProperty ;
    rdfs:label "authoringGuidance" ;
    rdfs:domain owl:Class ;
    rdfs:range xsd:string ;
    rdfs:comment "Guidance for whoever writes a document of this class — what belongs in the body. Delivered by `git lex create` and written into the class's __<Class>.md template; it does not land in the document itself. NEVER enforced: gates no save, raises no warning, absent from verify." .

git-lex:NamedGraph a owl:Class ;
    rdfs:subClassOf subtexture:NamedGraph ;
    rdfs:subClassOf sd:NamedGraph ;
    rdfs:label "NamedGraph" ;
    rdfs:comment "A named graph git-lex maintains in a repo's knowledge base. Instance IRIs are derived under the git-lex application slug and typed against this class in the default graph for zero-knowledge discovery. Note the two-resources distinction: the NamedGraph OBJECT (this IRI) is the queryable thing that describes the graph; the graph it names is carried via sd:name. Anchored to the W3C SPARQL Service Description sd:NamedGraph — the one external supertype git-lex adopts, because it is a real standard class, not an invented layer." .

git-lex:LexHistoryGraph a owl:Class ;
    rdfs:subClassOf git-lex:NamedGraph ;
    rdfs:label "LexHistoryGraph" ;
    rdfs:comment "The one graph: a repo's statement history as a named graph — base facts alongside the SpoEvents that record their lifecycle. 'Now' is a view derived from it (see git-lex:SpoEvent). Each repo's store holds exactly one, named https://repolex.ai/git-lex/LexHistoryGraph (a per-store singleton — no instance segment needed). Distinct by name from the retired legacy history graph. Event ordering within the graph follows git2:ordinalDerived." .

# =============================================================================
# Properties
# =============================================================================

git-lex:name a owl:DatatypeProperty ;
    rdfs:label "name" ;
    rdfs:range xsd:string ;
    rdfs:comment "Human-readable name. Used on tracked documents, and on the repo node (repo.yml `name:`)." .

# =============================================================================
# The repo (git-lex:Repo)
# =============================================================================
# What git-lex knows about the repository it manages, beyond what git2 sees.
# One property per .lex/repo.yml key — what's in repo.yml is what's queryable,
# no curation layer. The node keeps its ruled IRI
# (git-lex/git2/Repository/<genesisSha>) and carries BOTH types
# (git-lex:Repo + git2:Repository) — the path names the git2-layer identity,
# the git-lex type adds the management layer. repo.yml's `first_commit` does
# NOT become a property: it IS genesisSha (computed from git; the yml
# duplicate retires in config cleanup). `name` is git-lex:name above.

git-lex:Repo a owl:Class ;
    rdfs:subClassOf git2:Repository ;
    rdfs:label "Repo" ;
    rdfs:comment "A git repository under git-lex management: a git2:Repository that additionally carries .lex/repo.yml — its kit, genesis identity, and the repo facts git-lex itself maintains." .

git-lex:genesisSha a owl:DatatypeProperty ;
    rdfs:domain git-lex:Repo ;
    rdfs:range xsd:string ;
    rdfs:label "genesisSha" ;
    rdfs:comment "The full 40-character SHA of the repository's first (genesis) commit — the repo's stable identity across renames and remotes, and the derivation of its node IRI. Computed from git; git2 has no genesis concept, so the concept is git-lex's." .

git-lex:kit a owl:DatatypeProperty ;
    rdfs:domain git-lex:Repo ;
    rdfs:range xsd:string ;
    rdfs:label "kit" ;
    rdfs:comment "The base kit installed in this repo (repo.yml `kit:`), e.g. repolex-ai/git-lex-kit-soul." .

git-lex:created a owl:DatatypeProperty ;
    rdfs:domain git-lex:Repo ;
    rdfs:range xsd:date ;
    rdfs:label "created" ;
    rdfs:comment "When the repo was initialized by git-lex (repo.yml `created:`)." .

git-lex:agentName a owl:DatatypeProperty ;
    rdfs:domain git-lex:Repo ;
    rdfs:range xsd:string ;
    rdfs:label "agentName" ;
    rdfs:comment "The agent this repo belongs to (repo.yml `agent_name:`)." .

git-lex:agentEmail a owl:DatatypeProperty ;
    rdfs:domain git-lex:Repo ;
    rdfs:range xsd:string ;
    rdfs:label "agentEmail" ;
    rdfs:comment "The agent's email (repo.yml `agent_email:`)." .

git-lex:version a owl:DatatypeProperty ;
    rdfs:domain git-lex:Repo ;
    rdfs:range xsd:string ;
    rdfs:label "version" ;
    rdfs:comment "The repo config version (repo.yml `version:`)." .

git-lex:optionalKit a owl:DatatypeProperty ;
    rdfs:domain git-lex:Repo ;
    rdfs:range xsd:string ;
    rdfs:label "optionalKit" ;
    rdfs:comment "An optional kit installed in this repo (repo.yml `optional_kits:`). Multi-valued — one triple per kit." .

# git-lex:substrate was HERE, on Repo, meaning "a harness declared in repo.yml".
# Removed in v0.12 and the name reassigned to the Thing plane — see the changelog
# block at the top of this file. REPO HAS NO SUBSTRATE PROPERTY.
#
# READER STILL LIVE: git2_nquads.rs emits a `substrate` triple per repo.yml
# `substrates:` entry, onto the REPO node, via a hardcoded allowlist (:504) that
# never consulted this declaration. Until that emitter is removed, a repo that
# declares substrates will put harness names on the same predicate that now means
# a model id. W4R3Z's, flagged at the ruling.

# =============================================================================
# The statement lifecycle (SpoEvent)
# =============================================================================
# git-lex's append-only record of every statement's lifecycle. A statement
# (one s-p-o triple, e.g. one tag value in frontmatter) is identified by its
# RDF 1.2 triple term <<( s p o )>> — the W3C-standard representative of a
# statement, which needs no class of its own. What DOES need a class is the
# EVENT: the dated fact that a statement entered or left the graph in a
# specific commit. Everything here is derived deterministically from the
# repo's documents and commits — nothing is created except by git itself.
#
#   git-lex:SpoEvent/<eventId>
#       rdf:type            git-lex:SpoEvent ;
#       rdf:reifies         <<( s p o )>> ;         # the statement
#       git-lex:assertedIn  git2:Commit/<sha> .     # OR retractedIn — never both
#
# Structural contract (enforced by the emitter's integrity checks, not here):
# exactly one rdf:reifies; exactly one of assertedIn XOR retractedIn. The
# event IRI is derived from the event's identity: triple + commit + direction.
# NOTE for emitters: Turtle 1.2's annotation syntax {| ... |} ASSERTS the base
# triple as it annotates — usable for assert events only; a retraction must
# reference its statement WITHOUT asserting it, so the bare reifier form above
# is used uniformly for both directions.

git-lex:SpoEvent a owl:Class ;
    rdfs:label "SpoEvent" ;
    rdfs:comment "One temporal event in a statement's lifecycle: the statement (identified by the rdf:reifies triple term) entered the graph (assertedIn) or left it (retractedIn) in a specific commit. The assert and any later retract of the same statement are separate events; latest-event-wins gives current state. Instances live in the one graph, alongside the base facts they chronicle. The store maintains no separate 'now' graph: within the store, 'now' is a VIEW — a query, or a materialized graph, of the latest assertions that have not been retracted — derived from this one graph. (git-lex's direct query path — an ephemeral passthrough of git's current info, never stored — is a separate concern, outside this model.) Multivalued frontmatter is a SET of independent facts — each value is its own statement with its own independent lifecycle, diffed as resolved sets per commit; order and duplicates are not represented. Per-statement retraction is what makes multivalue work: a removed value has no superseding assertion, only its retraction event." .

git-lex:assertedIn a owl:ObjectProperty ;
    rdfs:domain git-lex:SpoEvent ;
    rdfs:range git2:Commit ;
    rdfs:label "asserted in" ;
    rdfs:comment "The commit in which this statement entered the graph." .

git-lex:retractedIn a owl:ObjectProperty ;
    rdfs:domain git-lex:SpoEvent ;
    rdfs:range git2:Commit ;
    rdfs:label "retracted in" ;
    rdfs:comment "The commit in which this statement left the graph. Nothing is deleted (events are append-only); this event records that the statement is no longer asserted as of this commit." .
