The text KB format — one file per context, one s-expression per sentence — read and written.
It is the format the shipped ontology is authored in (resources/kb/,
vaelii.impl.seed), and this is where the writer for it lives, so a KB an author
edited as text can be got back out of a store as text. The three formats a KB moves
in are different questions and stay separate doors:
| what it holds | who reads it | |
|---|---|---|
| text (here) | premises, in the author's own spelling, no handles | assert |
export dump (io.export / io.import) | every record and justification at its own handle | import! |
| the store itself | the live KB | the engine |
A dump is a KB's state; text is a KB's content. Only the second survives a re-derivation, a rename or an engine that concludes something new — which is what an author editing an ontology wants, and what a dump deliberately is not.
Every form is one sentence, read with clojure.edn, so a KB file is data and can
never run code. ;; comments and blank lines are free — the reader is the EDN
reader, so it skips them without a line-oriented pass. The file name is the
context: CxKinship.txt asserts into CxKinship, and a sentence for another
context says so with (ist Cx S) as it would anywhere else. A rule carries its
set/*Rule / set/defaultRule wrappers and its exceptWhen exactly as an author
writes them, because that is what assert reads.
One wrapper is not part of the sentence: (set/monotonic S), the known-true
class. A strength is an option on the assertion rather than part of the sentence, so
there is nowhere in an s-expression for it to go, and a text KB that could not say it
would round-trip a KB's monotonic premises down to defaults. It is peeled by
load-entries! below into {:strength :monotonic} and never reaches the store as a
functor. :default is the door's own fallback and is written as nothing, which is why
no shipped file carries a wrapper.
An exceptWhen states a strength per half, because it asserts two things: the rule,
and the exception qualifying it. The outer wrapper is the assertion's own option and
assert gives it to both, so it can only state a class the two share; a wrapper on
the query states the exception's own, and assert reads that one itself
(sentex/peel-exception-strength), so a hand-written KB spells it the same way. Four
pairings, four spellings:
| rule | exception | written as |
|---|---|---|
| default | default | (exceptWhen Q R) |
| default | known-true | (exceptWhen (set/monotonic Q) R) |
| known-true | default | (exceptWhen Q R), and (set/monotonic R) on a line of its own |
| known-true | known-true | (set/monotonic (exceptWhen Q R)) |
The third takes two lines because there is no wrapper for weakening a half: the rule
gets a line of its own at the stronger class, and mark-premise resolves a premise
asserted twice to the stronger of the two.
Premises only. A derived sentex is what the engine concluded from the premises, so writing it out would store as a premise what the KB believes as a conclusion — a reload would hold it against retraction of everything it followed from. Chaining puts it back at load, which is the whole point.
No handles. A text KB is re-asserted rather than restored, so it lands at
whatever handles the loading KB mints. A caller who needs handle identity across the
round trip wants export!, not this.
Deterministic. Files are named for their contexts and their forms are ordered by
content (nm/by-print-key), never by handle — so two KBs holding the same knowledge
export byte-identical files whatever order they were built in
(docs/defenses.md, "Tie-breaks and orderings key on content").
The **text KB format** — one file per context, one s-expression per sentence — read
and written.
It is the format the shipped ontology is authored in (`resources/kb/`,
`vaelii.impl.seed`), and this is where the writer for it lives, so a KB an author
edited as text can be got back out of a store as text. The three formats a KB moves
in are different questions and stay separate doors:
| | what it holds | who reads it |
|---|---|---|
| **text** (here) | premises, in the author's own spelling, no handles | `assert` |
| **export dump** (`io.export` / `io.import`) | every record and justification at its own handle | `import!` |
| the **store** itself | the live KB | the engine |
A dump is a KB's *state*; text is a KB's *content*. Only the second survives a
re-derivation, a rename or an engine that concludes something new — which is what an
author editing an ontology wants, and what a dump deliberately is not.
## The format
Every form is one sentence, read with `clojure.edn`, so a KB file is data and can
never run code. `;;` comments and blank lines are free — the reader is the EDN
reader, so it skips them without a line-oriented pass. **The file name is the
context**: `CxKinship.txt` asserts into `CxKinship`, and a sentence for another
context says so with `(ist Cx S)` as it would anywhere else. A rule carries its
`set/*Rule` / `set/defaultRule` wrappers and its `exceptWhen` exactly as an author
writes them, because that is what `assert` reads.
**One wrapper is not part of the sentence**: `(set/monotonic S)`, the known-true
class. A strength is an *option* on the assertion rather than part of the sentence, so
there is nowhere in an s-expression for it to go, and a text KB that could not say it
would round-trip a KB's monotonic premises down to defaults. It is peeled by
`load-entries!` below into `{:strength :monotonic}` and never reaches the store as a
functor. `:default` is the door's own fallback and is written as nothing, which is why
no shipped file carries a wrapper.
**An `exceptWhen` states a strength per half**, because it asserts two things: the rule,
and the exception qualifying it. The outer wrapper is the assertion's own option and
`assert` gives it to *both*, so it can only state a class the two share; a wrapper on
the **query** states the exception's own, and `assert` reads that one itself
(`sentex/peel-exception-strength`), so a hand-written KB spells it the same way. Four
pairings, four spellings:
| rule | exception | written as |
|---|---|---|
| default | default | `(exceptWhen Q R)` |
| default | known-true | `(exceptWhen (set/monotonic Q) R)` |
| known-true | default | `(exceptWhen Q R)`, and `(set/monotonic R)` on a line of its own |
| known-true | known-true | `(set/monotonic (exceptWhen Q R))` |
The third takes two lines because there is no wrapper for *weakening* a half: the rule
gets a line of its own at the stronger class, and `mark-premise` resolves a premise
asserted twice to the stronger of the two.
## What a text export holds, and what it does not
**Premises only.** A derived sentex is what the engine concluded from the premises,
so writing it out would store as a premise what the KB believes as a conclusion — a
reload would hold it against retraction of everything it followed from. Chaining
puts it back at load, which is the whole point.
**No handles.** A text KB is re-asserted rather than restored, so it lands at
whatever handles the loading KB mints. A caller who needs handle identity across the
round trip wants `export!`, not this.
**Deterministic.** Files are named for their contexts and their forms are ordered by
content (`nm/by-print-key`), never by handle — so two KBs holding the same knowledge
export byte-identical files whatever order they were built in
(docs/defenses.md, "Tie-breaks and orderings key on content").(context-of f)The context a text KB file asserts into: its name without the .txt.
The context a text KB file asserts into: its name without the `.txt`.
(entries source)Every [form context] a text KB holds, with the files they came from:
{:files ["CxCore.txt" …] :entries [[form context] …] :path "…"}.
source is a directory of Cx….txt files — an export-text! destination, or an
ontology tree an author keeps — or a single such file. Either way the context of a
form is its file's name, so a directory is read whole and handed to load-entries! in
one pass rather than a file at a time: the names sort alphabetically, which is not a
dependency order, and one context's content can rest on another's.
Every `[form context]` a text KB holds, with the files they came from:
`{:files ["CxCore.txt" …] :entries [[form context] …] :path "…"}`.
`source` is a **directory** of `Cx….txt` files — an `export-text!` destination, or an
ontology tree an author keeps — or a single such file. Either way the context of a
form is its file's name, so a directory is read whole and handed to `load-entries!` in
one pass rather than a file at a time: the names sort alphabetically, which is not a
dependency order, and one context's content can rest on another's.(kb-file? f)Is f a context file of a text KB directory — a regular Cx….txt? The prefix is
the discriminant so a directory holding a README or a checksum is read for its KB
files rather than refused for the rest.
Is `f` a context file of a text KB directory — a regular `Cx….txt`? The prefix is the discriminant so a directory holding a README or a checksum is read for its KB files rather than refused for the rest.
(kb-files dir)The context files of the text KB directory dir, in name order.
The context files of the text KB directory `dir`, in name order.
(load-entries! assert! entries)Assert every [form context] of entries through assert! —
(assert! sentence context opts) — order-insensitively: an entry refused because
content further down the list has not arrived yet is retried rather than fatal.
Returns how many entries were read.
assert! is passed in rather than required: this sits under vaelii.core, and the
two callers above it are vaelii.core/load-text! and vaelii.impl.seed.
A KB file's order is its terms', not its dependencies' — blocks run in natural sort
order, which is the whole point of grouping term-centrically — so a file cannot also
be dependency-ordered. (transitiveInArg largerThan 1 partOf) sits under largerThan
and (transitive partOf) under partOf, and l sorts before p. Several checks
read the store (a preservation's transitivity, an arg clash, a disjointness), so
without this a term-centric file is refused for where its author filed a sentence.
A whole directory is one pass, for the same reason one level up: the file names
sort alphabetically and a context's content can rest on another's. The context
topology goes first whatever order it arrived in (topology-first), which is the one
thing retrying cannot fix.
Retry rounds run while each one makes progress; the entries that survive a round that
changed nothing are re-asserted without a catch, so a genuinely ill-formed one
still throws — carrying the error it has once everything that could have helped it is
stored, which is the error worth reporting. A list that loads clean pays one try
per entry and no second round.
Assert every `[form context]` of `entries` through `assert!` — `(assert! sentence context opts)` — **order-insensitively**: an entry refused because content further down the list has not arrived yet is retried rather than fatal. Returns how many entries were read. `assert!` is passed in rather than required: this sits under `vaelii.core`, and the two callers above it are `vaelii.core/load-text!` and `vaelii.impl.seed`. A KB file's order is its *terms'*, not its dependencies' — blocks run in natural sort order, which is the whole point of grouping term-centrically — so a file cannot also be dependency-ordered. `(transitiveInArg largerThan 1 partOf)` sits under `largerThan` and `(transitive partOf)` under `partOf`, and `l` sorts before `p`. Several checks read the store (a preservation's transitivity, an `arg` clash, a disjointness), so without this a term-centric file is refused for where its author filed a sentence. **A whole directory is one pass**, for the same reason one level up: the file names sort alphabetically and a context's content can rest on another's. The **context topology goes first** whatever order it arrived in (`topology-first`), which is the one thing retrying cannot fix. Retry rounds run while each one makes progress; the entries that survive a round that changed nothing are re-asserted **without a catch**, so a genuinely ill-formed one still throws — carrying the error it has once everything that could have helped it is stored, which is the error worth reporting. A list that loads clean pays one `try` per entry and no second round.
(peel-strength form)[sentence opts] for one form of a text KB file: a (set/monotonic S) wrapper
becomes S with {:strength :monotonic}, and anything else is itself with nil.
The wrapper is refused rather than passed through when it is malformed — a bare
(set/monotonic) or one wrapping two sentences would otherwise reach assert as a
sentence whose functor is a namespace-qualified symbol, and be refused there for the
wrong reason.
`[sentence opts]` for one form of a text KB file: a `(set/monotonic S)` wrapper
becomes `S` with `{:strength :monotonic}`, and anything else is itself with `nil`.
The wrapper is refused rather than passed through when it is malformed — a bare
`(set/monotonic)` or one wrapping two sentences would otherwise reach `assert` as a
sentence whose functor is a namespace-qualified symbol, and be refused there for the
wrong reason.(read-forms source)Every form in the text KB file source (anything io/reader takes), in file order.
clojure.edn, so comments and blank lines cost nothing and no code can run.
Every form in the text KB file `source` (anything `io/reader` takes), in file order. `clojure.edn`, so comments and blank lines cost nothing and no code can run.
(write-kb! kb dir)(write-kb! kb dir o)Write kb's premises into dir as a text KB — one <Context>.txt per context —
and return a summary:
{:contexts n :sentences n :skipped n :files ["CxCore.txt" …]
:bytes n :elapsed-ms n :dir "…"}
opts narrows what is written: {:context C} for that one context's file, {:cone C}
for C and every context it sees. Neither, and every context with a premise in it is
written. A key this fn does not read is refused (:unknown-option) — a misspelt
narrowing writes the whole KB under a summary that looks right.
dir must be absent or empty. See the namespace docstring for what a text KB holds
and what it deliberately does not.
Write `kb`'s premises into `dir` as a text KB — one `<Context>.txt` per context —
and return a summary:
{:contexts n :sentences n :skipped n :files ["CxCore.txt" …]
:bytes n :elapsed-ms n :dir "…"}
`opts` narrows what is written: `{:context C}` for that one context's file, `{:cone C}`
for `C` and every context it sees. Neither, and every context with a premise in it is
written. A key this fn does not read is refused (`:unknown-option`) — a misspelt
narrowing writes the whole KB under a summary that looks right.
`dir` must be absent or empty. See the namespace docstring for what a text KB holds
and what it deliberately does not.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |