What a genl edge is worth. arity, functional, asymmetric and the three argument
constraints descend the predicate hierarchy now — at the door and on every retroactive
pass — so a claim spelled with a sub-predicate is held to what its supers declare, and
the six arrival orders of {declaration, fact, edge} reach one set of beliefs. Beside it
two structural gaps close: a KB whose derived state was never built refuses writes rather
than accepting them unchecked, and a firing rests on the genlCx edges its placement was
read over, so retracting one takes the conclusion back.
Twenty entries are Breaking, which is what makes this a minor rather than a point
release. Eight Refusal entries batch here rather than each forcing its own, which is what
§3.8 of CONTRIBUTING.md designates a minor for. Every Breaking and every Refusal entry
carries its Migration line, and every entry links the page that carries the mechanism —
an entry here says what moved and what to write instead, and the doc says how it works.
Triage, for a 0.7.0 caller. This is the index to what touches something you have written.
| If your code… | Then |
|---|---|
asserts a sub-predicate fact under a super's argIsa / argGenl / interArgIsa | refused :arg-type; widen the declaration, move it down, or drop the genl edge |
declares an arity that disagrees with its super's | refused whichever sentence arrives second; give the two one arity, mark either end variableArity, or drop the edge |
relies on (functional P) or (asymmetric P) binding P's exact functor alone | both convict a sub-predicate's tuples now, in either arrival order |
reads an empty violations as a clean bill | :arity and :constraint-exposure entries appear in arrival orders that filed none, and a retraction no longer files :no-placement |
branches on violations' :violation with a defaultless case | :arity-truncated and :arity-report-truncated are new kinds |
counts :arity entries to size a problem | both retroactive passes file at most 8 and carry the totals on a truncation notice |
writes to a KB opened {:recover? false}, or loaded :belief? false / :belief? :stored | refused :unrecovered-kb; call recover (or reindex) first, or bind *write-unrecovered?* |
| retracts against such a KB | refused, where it deleted the record and left its justifications dangling |
spells one sentence as a top-level vector — (assert kb '[likes Tom Ann]) | refused :shape; write the list, and ask a conjunction with query or prove |
passes a non-map where an option map goes, (open-kb :nope) among them | :unknown-option by name, where it threw an unnamed error |
reads why's :because, why-not's :missing or preview's :antecedents | a cross-context firing lists the genlCx edges it was placed over, and a descended merge names each genl edge once rather than once per side |
reads defeat-class on a cross-context conclusion | it caps on those edges, so a known-true fact read across a :default context edge answers :default |
re-asserts known-true content over a :monotonic premise | the stronger class survives the re-assert; narrowing one is retract! and re-assert |
asserts a rule concluding (rewriteOf …), (sameAs …), (equals …) or (disjointMetatype …) | it merges or separates while the KB runs, where only a restart saw it |
lists a store directory through catalog/classify | classification reads records/format.edn; three disk backends classify as :store that did not, and a records/+index/ pair alone no longer does |
catches around unload! | a store that did not close reports :unreleased, where it reported silent success |
runs kb-quality | a fifth reading, :declarations, names argument constraints that constrain nothing, and :stranded-count drops |
matches an arity refusal on its :message | an inherited length reads "takes N arguments through P", not "is declared with N" |
Breaking: such a KB refuses writes rather than accepting them unchecked. Every
definitional check bottoms out in jtms/in?, so over an empty network all ten match
nothing and the assert lands — and nothing later catches it. assert, assert-inert,
retract!, edit! and preview refuse by name (:unrecovered-kb), reporting
:hazards and naming the call that clears them.
Class: Breaking; writes that landed silently now throw.
Migration: call recover (or reindex) before writing, which is what the content
needed anyway; or bind vaelii.core/*write-unrecovered?* around the write, which now
logs once per KB naming what is unchecked.
Breaks: :unrecovered-kb, :unrecovered-premise, *write-unrecovered?*, :recover?
docs/storage.md, docs/web.md
Breaking: a derived record's teardown is refused where belief was never built.
retract-storage! read "no TMS node" as "inert" and deleted a forward-chained record,
leaving dangling the justifications that concluded it. Nothing per-handle separates the
two cases, so the question is asked of the KB instead.
Class: Breaking; a retraction that deleted a record now throws.
Migration: call recover (or reindex) before retracting, which is what the sweep
needed anyway.
Breaks: :unrecovered-kb
docs/storage.md
check and check-edit answer for the door they mirror. check-writable! runs
first at assert and was not in the stage list, so a batch validated against an
unrecovered KB came back admissible and then refused on its first line. Both report
:unrecovered-kb alone and first, and both go quiet under *write-unrecovered?*.
Class: Additive; a new problem :type on two readers that report problems.
Migration: none. A caller matching on the message rather than the :type sees
"index was" where the index hazard stands alone.
Breaks: :unrecovered-kb, check, check-edit
docs/storage.md
Refusal: a declared hazard survives being read while the store is still empty.
write-hazards retired import-dump's {:no-belief true} on any read taken before the
records landed, handing the finished load a KB whose records are unbuilt and whose
hazard is gone.
Class: Refusal; writes a prematurely-released hazard let through are now refused by
name.
Migration: code clearing a store through p/clear-records! rather than clear!
should call kb/note-hazards! with both keys false, as the suite's fixtures now do.
Breaks: write-hazards, note-hazards!
docs/storage.md
Refusal: recover stops believing a record the store does not hold. A stored
justification concluding a handle with no record minted a phantom and made it IN, so the
KB came back believing a handle no query could return, and everything drawn from it.
Such a justification is left out of the network and counted, logged once at :warn
under ::justifications-unrooted.
Class: Refusal; the handle read absent and its belief read true, so the state it
produced was not one anybody asked for.
Migration: nothing to change; a store carrying such a justification now says so.
Breaks: recover, justifications-unrooted
docs/storage.md
:belief? :stored — store what rests on what, and settle it later. Everything
true does except the recover, for a corpus that cannot afford one; for a foreign
dialect it is the only mode that keeps the justifications at all. The catalog carries the
choice (:rebuild / :stored / :skip) rather than a checkbox, and active-caveat
gained :recoverable? so the browser's banner names which repair applies.
Class: Refusal for the :belief? value check; Additive for the mode itself.
Migration: none — true is still the default. An unrecognised :belief? value is now
refused by name (:unknown-option), since anything truthy would otherwise mean true
and run the recover the caller asked to defer.
Breaks: :belief?, :unknown-option
docs/catalog.md, docs/web.md
One frame a dump holds and this build will not construct stops taking the load with
it. A frame the structural checks refuse yields no record, and both import paths threw
on it — while a dump is not a program being written, and the reading side cannot fix the
writing side. It is counted in the summary's :refused, skipped, and logged, which
is the policy the naming door has had all along.
Class: Additive for :refused and :frames.
Migration: a load that threw :naf-not-closed — or any other construction refusal —
now finishes; assert (zero? (:skipped (:refused summary))) to keep the old strictness.
docs/naming.md
A justification the import writes no longer rests on a record the import deleted. A
remapped load drops the meta-sentexes whose (sentexHandle H) will not resolve, but the
dump-id map went on resolving their ids, so both deduction readers stored justifications,
premise marks and provenance pointing at records that were no longer there.
forget-deleted closes it, reporting :orphaned-ids and
:dropped-justifications-orphaned.
Class: Additive for both keys; a load that wrote a dangling justification now drops it
and says so.
Migration: none. A store already carrying them is repairable in place — delete every
justification naming a handle sentex-ids does not yield.
docs/catalog.md
Refusal: an import frame that fills a justification's :out slot
(:naf-justification, a new :type). The slot is the NAF antecedent set — reserved,
and empty in every KB the engine builds — but a justification frame in a dump is the
record's own field map, which made that door the one way a filled one could reach a
store. Three relabel invariants read the slot as empty rather than reading it.
Migration: nothing — no dump the engine produced carries one.
Breaks: :naf-justification, import!, import-dump
docs/naf.md
A dump that fills that slot is refused before the import writes anything. The check
ran after the whole sentex phase had landed, and an import is not a transaction, so the
refusal left a half-written store that assert-empty-destination! then refused to retry
into. It streams the file in a pre-pass now; same :type, same message, same ex-data.
Class: neither label; the refusal is the same refusal, and what changed is what the KB
holds afterwards. docs/catalog.md, docs/naf.md
A restart stops answering through an edge nothing supports. rebuild-taxonomy
replays stored declarations, so a genl edge that is OUT from the moment its node is
made still answered isa? — a restart believing a type the running KB did not. recover
runs refresh-beliefs over the replay, before the settle.
Class: neither label; a restart's answers move onto the running KB's.
docs/storage.md, docs/taxonomy.md
Breaking: arity, asymmetric and functional descend it. Each read its mark off
the exact functor while the machinery it convicts with already fanned down the
hierarchy, so each was bypassable through a sub-predicate door. A predicate declaring no
arity of its own takes the one its supers agree on; (asymmetric parentOf) convicts
(fatherOf a b) beside (parentOf b a) in either order; (functional parentOf)
reconciles two fatherOf fillers. arity is the strict one: two declared arities
across one edge is refused. Supers that disagree bind nothing, a variableArity super
releases the inheritance, and the generative marks descend nowhere.
Migration: a specialization that genuinely reads a different number of arguments is
variableArity on either end of the edge; a sub-predicate declaring a conflicting arity
is refused, so give the two one arity or drop the genl edge.
Breaks: variableArity, asymmetric, functional, :arity,
Breaks: binaryPredicate, functional-clashes
docs/taxonomy.md, docs/inherit.md,
docs/equality.md
Breaking: an argument constraint on a predicate binds its sub-predicates' tuples.
(genl fatherOf parentOf) says every fatherOf tuple is a parentOf tuple, so
(argIsa parentOf 1 person) refuses (fatherOf TheRock1 Mary) exactly as it refuses the
claim spelled parentOf; argGenl and interArgIsa descend by the same argument. The
refusal was door-dependent and failed at the one job it exists for, the matcher fanning a
goal's functor over its subtypes. Held to the writer's vantage: an edge a context cannot
see imports no constraint.
Migration: a KB using predicate-level genl for retrieval fan-out alone, relying on
the specialized predicate being unconstrained, is refused where 0.7.0 accepted it; widen
the declaration, move it down, or drop the edge.
Breaks: argIsa, argGenl, interArgIsa, :arg-type
docs/taxonomy.md
argIsa read as an inference descends with it, and so does the entailment. ask
types an argument through a super-predicate's declaration, because a claim assert
refuses for being ill-typed must not be one ask cannot type at all. Under
*assertive-arg-types?* the minted type names the genl edges it descended, so
retracting one takes the type back, and the entailment is drawn when the edge arrives
last.
Class: Additive; the entailment is opt-in and off by default.
docs/argtypes.md
Three predicate-metadata kinds join has-prop? / props: :declares-arg-isa,
:declares-arg-genl, :declares-inter-arg-isa, marking a predicate that is the
subject of an argument constraint. The descension asks per super whether it declares
anything at all — off the index an argument-root probe per super on every assert, and a
set membership once it is marked. Class: Additive.
docs/taxonomy.md
Breaking: a functional or asymmetric mark reaches back down a genl edge, on both
retroactive paths. Under :arbitrate a mark or edge arriving after the facts left
the clashing pair believed — permanently, the pair never entering :clashes for a later
settle to re-derive. Both paths take their extent through the marked predicate's spec
subtree now.
Class: Breaking; a clashing pair that stood believed is now arbitrated.
Migration: a caller reading contradictions or violations sees pairs the door has
always refused when the mark arrived first — the two arrival orders agree now.
Breaks: contradictions, violations, :constraint-exposure
docs/taxonomy.md
Breaking: the cross-context exposure pass reads its marks down the hierarchy, as every
check it gates already did. It read the mark off the exact functor per sentex, so a
pair whose only mark sits on a super-predicate was dropped before any check saw it.
Class: Breaking; a KB holding such a pair files a :constraint-exposure entry where it
filed nothing.
Migration: nothing to write — the entry names both handles, so the pair is what to look
at.
Breaks: violations, :constraint-exposure
docs/contexts.md, docs/taxonomy.md
Breaking: the retroactive arity report descends the hierarchy, as the door it mirrors
already does. Fact, declaration and genl edge are three ingredients and any can
arrive last; the report read only the predicate its trigger named, so a ternary
fatherOf fact under a binary parentOf stood believed and unmentioned. It sweeps the
spec subtree now, and entries carry :via.
Class: Breaking; nothing new is refused, and a caller reading violations sees a
finding it did not.
Migration: nothing to write — read :via to tell an inherited length from a declared
one.
Breaks: violations, :arity, :via
docs/taxonomy.md
Breaking: that report answers a genlCx edge, its fourth ingredient. A visibility
edge rebinds a predicate's length as a genl edge does, and settle/arity-bound-by knew
three spellings and not that one. The pass triggers on the edge and sweeps whichever end
p/count-in-context sizes smaller.
Class: Breaking; :arity entries appear in two arrival orders that filed none.
Migration: none for a KB whose contexts declare their own arities; one declaring in a
super-context learns about facts that were already wrong.
Breaks: violations, :arity
docs/taxonomy.md, docs/contexts.md
Breaking: the arity door words an inherited length as inherited, as its retroactive
half already did. fatherOf is declared with 2 arguments, declared of parentOf but has 3 credited fatherOf with a declaration it never carried; it reads fatherOf takes 2 arguments through parentOf but has 3 now, with a self-declared length unchanged.
Class: Breaking on §3.8's counterweight: only the :message string moves, which is
the class-1 test.
Migration: read :expected, :actual and :via off the ex-data rather than matching
the message.
Breaks: is declared with, :opposing-handle, :arity
docs/taxonomy.md
Breaking: a variableArity predicate may be given argument types past its declared
length. arg-position-problem was the one arm of the arity family that did not read
checks/variable-arity?, so (argIsa qRel 3 person) was refused on a predicate whose
3-argument facts the same KB admitted.
Class: Breaking; input that was refused is now admitted, and kb-quality's
:stranded-count drops.
Migration: nothing in the shipped ontology moves — none of its three variableArity
predicates declares past its length.
Breaks: variableArity, argIsa, :stranded-count, kb-quality
docs/taxonomy.md, docs/quality.md
kb-quality gains a fifth reading: argument constraints that constrain nothing.
(argIsa parentOf 3 person) is admitted while parentOf has no declared length; when
one arrives, declared or inherited, the declaration is left naming a position the
predicate provably does not have. :declarations names them and quality-report writes
the section. Deliberately not violations: a stranded declaration is inert and reads
the same an hour later.
Class: Breaking for the :arg-position refusal message, which now splits on :via as
the door and the report do; Additive for the reading itself.
Migration: read :via and :arity off the ex-data rather than parsing :message.
Breaks: kb-quality, quality-report, :arg-position, is declared with
docs/quality.md, docs/taxonomy.md
Breaking: neither retroactive pass can file its way through the ledger, and
:arity-report-truncated says when a cap stopped one. The ledger keeps the newest
1,000 entries and the arity report filed one per convicted predicate against a budget of
4,096, so one binding over a wide subtree could evict every other violation in the KB.
Both passes file at most the content-first 8 and say what the cap left out. Read the
new kinds as found, examined, and not named.
Class: Breaking for the cap, Additive for the kind and the keys.
Migration: size a problem from :predicates and :facts on the notice rather than by
counting :arity entries; past 8 the count is now visibly not the total.
Breaks: violations, :arity-report-truncated, :arity-truncated,
Breaks: :constraint-exposure-truncated
docs/taxonomy.md, docs/nmtms.md
The retroactive arity sweep says when its budget stopped it, including in the case that
carries no finding. The :truncated flag rode on a finding, so a predicate that spent
the budget convicting nothing left every predicate after it examined zero facts deep in
silence. One :arity-truncated entry is filed either way.
Class: Additive; a new :violation kind, so a defaultless case over them has one
more to admit. docs/taxonomy.md
Breaking: a descended merge names each genl edge once, not once per side. Both
sides of a functional clash reach their mark by their own path, and those paths are a
set: two fatherOf fillers under (functional parentOf) descend one edge, which
landed twice in the stored record and two or three times in the reports that read it.
Belief never moved, but an antecedent list is the explanation a caller is handed.
Class: Breaking on §3.8's counterweight: the list is shorter, deterministically so.
Migration: nothing, unless you counted.
Breaks: why-not, preview, :because, :antecedents
docs/nmtms.md
Breaking: a firing rests on the genlCx edges its placement was read over, and now
names them. A conclusion is placed in the maximal contexts that see the rule, the facts
and the genl edges the match subsumed through, and each sighting is a reachability some
ordinary sentex supports and somebody can take back. The justification named the
ingredients and not the edges, so retracting one left the conclusion believed in a context
that could no longer see any of its reasons — belief as a function of arrival order. The
edges join the antecedent list, one shortest path per ingredient context.
Migration: assert the genlCx wiring {:strength :monotonic} wherever a cross-context
conclusion must stay indefeasible, and expect context edges among a justification's
antecedents. Two things move for a caller who retracts nothing: antecedent lists are
longer, and defeat-class caps on the edges like any other ground.
Breaks: genlCx, supporting-justifications, defeat-class, :monotonic
docs/contexts.md, docs/nmtms.md
Breaking: a retraction stops filing :no-placement entries about the rules it just
took apart. A conclusion that now has nowhere to be placed is the retraction the
caller asked for, not a diagnosis of anything, and one per affected rule crowded the
ledger a caller reads for what it did not mean to do. An ordinary firing that cannot
place its conclusion still says so.
Class: Breaking; a public reader returns fewer entries.
Migration: none for a caller reading violations for problems. A caller counting
entries across a retraction sees the count it would have had if the rules had never been
asserted.
Breaks: violations, :no-placement
docs/contexts.md
A rule generator may stamp a generator, at any depth, and a variable an enclosing level
fills may head a literal. (implies (typeVersion ?ipred ?tpred) (implies (?tpred ?type ?cap) …)) states a type-level/instance-level bridge once instead of once per predicate
pair, and what reaches the index is still an ordinary rule over concrete functors. The
scoping rule needed nothing added, and a top-level rule antecedent is untouched.
Class: Additive — a shape that was refused is now accepted.
docs/generators.md
Breaking: a rule concluding (rewriteOf A B) / (sameAs A B) / (equals A B)
merges. The conclusion reaches the arm an asserted equality reaches, so the closure
learns the edge and every sentex naming the retired spelling gains a justified twin.
Before, a running KB and its own restart disagreed about whether two terms were one thing.
Migration: a KB with such a rule now merges where it did not, which moves matches,
different answers and belief; if the rule meant something weaker than identity, restate
it under a predicate of your own.
Breaks: rewriteOf, sameAs, equals
docs/equality.md
Breaking: a rule concluding (disjointMetatype M) separates M's members while the KB
runs. The mark reached the taxonomy only when a restart replayed it, so one store
answered disjoint? two ways either side of one.
Migration: a fact contradicting such a separation is refused :disjoint at assert time
now, where it was stored unchallenged.
Breaks: disjointMetatype, :disjoint, disjoint?
docs/taxonomy.md
Breaking: a re-asserted fact keeps the stronger defeat class. The premise mark was
last-writer-wins, so a bare re-assert of known-true content retired it and the
:monotonic negation then defeated the original — where the same three sentences
without the re-assert in the middle leave an irreducible clash. The class resolves from
content at the fact door as it already did at the rule door.
Migration: narrowing a class is retract! and re-assert, as it is for a rule's
:direction, :defeasible and :strength.
Breaks: :strength, defeat-class
docs/nmtms.md, docs/canonicalization.md
A justification reports as content, in both directions. 0.6.0 closed every report and
election that keyed on retrieval order; these are the two justification surfaces it left.
dependent-justifications handed back an allocation-ordered id set unsorted, so two
assertion orders of one KB listed the same dependents in opposite orders on a public API;
and a firing's stored antecedent vector is ordered where it is built, since a firing
is seeded by whichever antecedent triggered it. One carried from an earlier release keeps
the order it was written with until it is re-derived.
Class: neither label; the order it displaced was a function of how the KB was loaded, so
nothing stable was there to depend on.
docs/nmtms.md, docs/api.md
The wholesale wipe stops carrying the qualitative join baselines. clear! left
:qcn-joined standing beside the network cache it reset, describing a KB the call had
just deleted. Hygiene rather than correctness — a stale baseline self-invalidates through
its handle-subset check — but the wipe is the one thing that reaches a baseline.
Class: none; resident engine state with no caller-visible surface.
docs/qcn.md
why builds its proof tree over an explicit work stack, not the JVM stack. The walk
was real recursion capped at a depth of 256 — but the cap is a ceiling on the tree, not
a fix: a chain down a long transitive closure repeats no handle, so the cycle guard never
fires, and a {:max-depth n} past the JVM's frame budget overflowed on a KB merely large.
The walk is iterative now, so the cap bounds the size of the tree returned and nothing
overflows however deep the derivation runs. A regression test pins it on a deliberately
small stack, since the depth a recursion tolerates is the platform's and not the engine's.
Class: neither label; the tree returned is identical, and the one input whose behaviour
moves — a derivation deeper than the JVM's frame budget — returns its tree where recursion
threw StackOverflowError.
docs/api.md
Breaking: a top-level vector sentence is refused at both families of door (:shape).
A vector is sequential?, so assert flattened it to the list it looks like — while a
vector goal is what every read door spells a conjunction with. One spelling, two
doors, opposite answers, neither raising: (assert kb '[likes Tom Ann]) stored the list
and ask found it on the vector, while prove and query joined three symbols and
answered nothing; and ask flattened the documented goal [(dog ?y) (parentOf Tom ?y)]
into a sentence nothing matches and answered false. Nested vectors are untouched, as
is lookup, whose level 0 reads a vector as an index path.
Class: Breaking; a caller who wrote the vector spelling on both sides had code that did
what its author believed, and it stops on upgrade.
Migration: write one sentence as a list — (likes Tom Ann) — and ask a conjunction with
query or prove, which are the doors that join.
Breaks: :shape, sentexes-matching, handle-of, ask-within, prove-within,
Breaks: query-plan, provable?, query?, abduce, assert-inert, check-edit
docs/api.md, docs/troubleshooting.md
Refusal: a nil conjunct is a conjunct, not the absence of one. The guard read the
value of the first non-sentence member, so [(dog ?x) nil] passed it and the join then
answered nothing — a real conjunct silently zeroed, which is the number nobody can check.
It tests whether one exists now.
Migration: nothing — the goal never did what whoever wrote it believed.
Breaks: :shape, :conjunct
docs/api.md
Ten option doors word their refusal the same way, and open-kb gains the shape check.
The key check was written out at each door, so the wording drifted and one door was
missing half of it: (open-kb :nope) came back as a bare IllegalArgumentException about
creating an ISeq, where every other public entry point answers :unknown-option. With the
doors sharing one refusal (vaelii.impl.opts) four messages change wording and poll's
ex-data gains the :unknown key the others carried; :type and every other ex-data key
are unchanged at all ten. Separately, query's non-map refusal reports the value it
rejected under :got rather than under :options, which everywhere else is the roster.
Class: Refusal for open-kb's non-map, which previously threw an unnamed error.
Migration: none for a caller discriminating on :type. A caller matching refusal text
should match :type :unknown-option and read :options / :unknown instead.
Breaks: :unknown-option, open-kb
docs/namespaces.md
Breaking: a store on disk is recognised by the format marker it writes, not by a
directory pair. catalog/classify read a records/ beside an index/, which three
disk backends never write — so a store the browser could open listed as nothing at all,
while a pair left by something else listed as a store and failed on open. It reads
records/format.edn now.
Class: Breaking; a directory's classification changes in both directions.
Migration: none for a store this build wrote. A catalog entry pinned by a caller's own
path should be re-listed.
Breaks: classify, :store
docs/catalog.md
Refusal: unload! reports the release it actually performed, and gives way to the walk
it would have emptied. A close-dir! that threw was logged and the entry dropped, so an
unload reported clean over a store whose index had not fsynced; the entry keeps its place
with status :unreleased now, stops being the active KB, and a later unload retries.
An entry whose KB a running export is still walking is refused :still-exporting —
the walk has no snapshot.
Migration: nothing for an unload nothing else is holding, which is every unload that
succeeds; a caller catching around unload! sees :unreleased where a store that did not
close reported silent success.
Breaks: unload!, :unreleased, :still-exporting, reset-registry!
docs/catalog.md
Every kind violations can file has a row in the table consumers branch on. It named
six of the thirty, and all six rows carried :detail keys the entries no longer build.
violation_roster_test scans the sources both ways.
Class: neither label; no kind is new or moved. docs/api.md
Refusal: a two-axis calculus checks its projection is a bijection. The cardinal
directions and the relative frame are one algebra — nine relations that are two
independent coordinates on two axes — built now by vaelii.impl.projection from a single
table each. A table that is not a bijection onto the nine axis pairs is refused where it is
built (:bad-algebra) rather than composed: a missing pair composes to nil and stores it
as a relation, and a repeated one still covers all nine while the inverse silently drops
one.
Migration: nothing — both shipped tables are bijections; a caller building their own
calculus gains the refusal.
Breaks: :bad-algebra
docs/qcn.md
asymmetric stops claiming it hands you irreflexivity. CxCore's definition said the
mark "makes ?predicate irreflexive too". It does not: conviction needs a believed
opposing claim, and a self tuple (?predicate a a) is its own mirror, so there is no
second claim to convict against and the door admits it. CxSize's note on largerThan,
which leaned on the same wrong step, is reworded to rest on preservation running downward.
Class: neither label; ontology content, which §3.8 exempts from the Breaking label
however far it moves an answer — and here it moves none, the engine having always behaved
this way. What changed is a description that told a reader to expect a refusal nothing
performs. docs/taxonomy.mdThree passes over the predicate hierarchy were quadratic in a batch of genl edges,
which is what a load writes. All three are measured and explained in
docs/taxonomy.md, "What a batch of edges costs the passes that read it";
the answers computed are identical, and all three are free where nothing is declared.
A settle reads its functional and asymmetric marks once, from the marked end. Four
gates asked the one question through tax/props-over, whose memo keys on the node a walk
began at, so nested roots shared nothing. settle/clash-marked-below walks the marks
down instead, once per pass. 1,000 askers over a 1,000-predicate chain go from 213 ms
to 1.1. Class: neither label. docs/taxonomy.md
A batch of genl edges costs the union of its subtrees, not the sum of them.
settle/report-arity-reach! expanded each edge's spec subtree separately, and none of it
was bounded — the instance budget counts facts examined and this examined none.
tax/specs-of-all seeds one traversal with every root: 512 edges go from 60.6 ms to 1.0.
Class: neither label. docs/taxonomy.md
The two retroactive genl-edge arms decide there is nothing to draw before reading the
subtree, not once per fact inside it. special/equate-under-edge had no gate at all, so
every genl write on every KB materialized the subtree's extent to discover nothing was
functional. No curve moves — subsumption-seeds walks the same subtree and must.
Class: neither label. docs/taxonomy.md
A check for a shape the sentence does not hold stops building a seq to find out. Seven
readings descended every sentence with tree-seq hunting a form almost none contain;
sentex/some-form and forms-where are the two walks they share now. 13x on a plain
one-antecedent rule assert and 25x on a six.
Class: neither label; same answers, same depth-first pre-order.
docs/canonicalization.md
A refused sentence stops paying to resolve a stack trace nobody prints. A checked
import counts what the front door refuses, so the throw is a reporting path taken a hundred
thousand times in a load — and three quarters of it was ex-info materializing the trace
to elide its own two frames. check! builds the ExceptionInfo directly.
Class: neither label; same class, same message, same :type :naming ex-data.
docs/naming.md
A justification listing builds its content key once per entry, not once per comparison.
sort-by calls its key fn from inside the comparator, and a rule handle is an antecedent
of every firing it licenses, so dependent-justifications paid the multiple on the whole
history. Decorate, sort, undecorate.
Class: neither label; the same compare over the same keys, both sorts stable.
docs/nmtms.md
The whole matrix at once. lein test-matrix runs the eight storage backends and the
five sweeps concurrently, one JVM per configuration: ~13 minutes against the ~55 the two
single-axis scripts take in sequence, which is the difference between a check that gets run
before landing and one that gets skipped. Each configuration records the revision it
compiled and the report says whether they agree; a red run names the failing tests,
rolled up across configurations. Class: neither label.
docs/operations.md
Every verdict names the tree it is a verdict about. lein gate, test-backends,
test-sweeps and the sharded test stage print the revision and the src//test/ dirty
state on their banner, every summary row and every log they write: a count read an hour
later is only comparable against the tree it was taken on. Progress splits by reader —
mark rows for a terminal, one line per namespace for a pipe or CI, forced either way with
SUITE_PROGRESS. Class: neither label. docs/operations.md
Breaking: a context name is Cx-prefixed, not Context-suffixed. CoreContext
is CxCore, UniverseContext is CxUniverse, and the assert front door refuses a
Context-suffixed name by the same naming check that already refused a malformed
predicate or type. Migration: respell every context name — in a stored KB, an
assert call, and a saved dump — to the Cx form. docs/naming.md.
Breaking: the context-transitivity predicate is genlCx. (genlContext sub super)
is (genlCx sub super), so the relation between two contexts is spelled the way the
contexts it relates are. The genl closure over types keeps its name. Migration:
respell the predicate wherever an edge is asserted, matched or retracted; a stored
genlContext edge is a fact under a predicate nothing reads, so re-assert it rather
than expecting the taxonomy to find it. docs/taxonomy.md.
What a stored rule is worth. A rule can conclude a rule, a NAF guard written as a conjunction guards instead of firing unconditionally, and every door that reaches a rule reads belief rather than storage. Beside them, the arrival-order dependences left in the belief loop are closed — a revived datum, an un-merged spelling, and every report, digest and election that keyed on retrieval order — and two reads that grew with what the KB holds rather than with what the write touched now read forward off the region.
Three entries are Breaking, which is what makes this a minor rather than a point
release: the daemon answers an export refusal 400 where it answered 500, the model's tool
surface drops two ops, and the CLI's refusals move to stderr. Seven Refusal entries batch
here rather than each forcing its own release, which is what §3.8 of CONTRIBUTING.md
designates a minor for. Every Breaking and every Refusal entry carries its Migration
line.
Triage, for a 0.5.1 caller. This is the index to what touches something you have written.
| If your code… | Then |
|---|---|
treats a 5xx from the daemon's :export op as a backend fault | the five destination refusals answer 400 now; retry logic keyed on 5xx stops retrying a caller mistake |
drives :preview or :clear-caches through the LLM tool surface | they are no longer exposed to a model — call the op on the daemon or the API directly |
writes (ist Ctx S) in an antecedent or an exceptWhen | refused :not-well-formed; say it with decontextualizedPredicate or a genlCx edge |
passes (ist Ctx S) to a read | it answers now instead of returning empty, with the named context winning over the argument |
writes (unknown (and A B)) as a guard | it guards now; it fired unconditionally before. Under a quantifier the same shape is refused |
branches on violations' :violation with a defaultless case | :functional, :asymmetric and :constraint-exposure-truncated are new kinds |
runs check over (not (implies …)) | refused :not-well-formed at both doors, where check passed it and assert threw |
runs a :refuse KB and reads an empty violations as a clean bill | cross-context functional and asymmetric pairs are reported there now |
passes --strength to the CLI's assert-rule, or reads CLI refusals off stdout | the flag is honoured now, and refusals print on stderr |
forks a KB with an opts map naming neither :space nor :dir | the fork lands on its own space instead of the shared process default |
asks a symmetric predicate about a claim that is inherited rather than stored | the mirror composes with the other provers now, so an ask can answer more |
writes a kind-level (hasCapability <kind> …) against the shipped ontology | kind-level claims are capabilityType; hasCapability is the instance-level reading alone |
A capability claim about a kind is capabilityType, and about a member is
hasCapability. One symbol read at two levels gets one argument check, so seven facts
the starter shipped were convicted by a declaration the starter also shipped — silently,
the declaration and the facts sitting in different contexts. The kind-level content moves
to capabilityType, a typeRelationPredicate carrying the argPreserving pair;
hasCapability keeps argIsa … 1 animal. argPreservingInverse answers the six
conclusions that vanish with the kind-level rule. Two new sweeps put every shipped
sentence to check against the fully loaded KB and every stored fact to the declarations
the same KB ships — either alone misses what the other finds. The typeToInstancePred
pairing stays prose: hasCapability cannot carry the mark without changing its argument
family. Class: none; resources/kb/ is data rather than surface (§3.8).
docs/inherit.md, docs/taxonomy.md
A guard keyed on the operator instead of on what it reads. An exceptWhen whose
query is itself a query operator — unknown, a thereExists, an aggregate — was indexed
under a functor no sentex carries, so no arriving fact could queue it: the exception was
evaluated once and re-evaluated never, blocking forever including after the guard should
have released, and the stratification graph read the same keys, so a cycle through one
was refused nowhere. rules/watched-predicates peels the frames for all four sites that
key on them. The settle-time narrowing peels them too, or the corrected key would have
bought a quadratic — 48 → 192 level-6 evaluations for the same six triggers, against 0
once peeled. And check predicts the NAF-literal refusals rather than only assert
throwing them: they lived in the constructor, so the dry-run door could not see them.
Class: neither label for the guard — belief moves for a rule of this shape, and the
guard answered from arrival order rather than content; Additive for check.
docs/exceptions.md, docs/naf.md
A rule can conclude a rule. (implies <antes> (implies <antes'> <conseq'>)) is a
generator: its firing stores the rule it concludes, holes filled. The hole split is
computed rather than declared — a variable the generator's own antecedents mention is
bound by the join and ground in the mint, every other survives as a variable — so there
is no template vocabulary to disagree with the template it annotates, and a hole may
stand in functor position, which is the point: one generator ranges over a family of
predicates while every rule the index keys on has a concrete functor. A mint is derived
content, justified by the firing rather than marked a premise, so retracting what
licensed it un-believes it through the ordinary relabel. Rules follow belief now at all
four chainer sites, which is what makes that work. Five refusals bound it: a generator
generating a generator, an exceptWhen on the stamped rule, a set/backwardRule
generator, one sharing no variable with what it stamps, and a generator cycle —
refused outright rather than depth-capped, since a cap makes the KB's contents a function
of how long the chainer ran. Class: Additive; shapes that were refused are accepted,
and nothing could previously make a stored rule un-believed.
docs/generators.md
A NAF guard written as a conjunction now guards. (unknown (and A B)) was accepted
and inert in three places at once: no prover claims the functor and, so the goal came
back unanswered and read as not derivable — the unknown holding, the rule firing
unconditionally — while the re-check index posted it under a predicate no fact carries
and the stratification check drew its negative edge from the same functor. An author who
wrote a two-condition guard got a rule with no guard at all. sentex/naf-query-conjuncts
is the one accessor all four readers share, so the conjuncts are evaluated by the
exception's own block-if-all evaluator, each conjunct's predicate is watched, and each is
a negative edge; conjuncts are sorted and flattened, as an exception's are. A conjunction
under a quantifier is refused (:quantified-conjunction) — read flat, each conjunct
would be satisfied by a different witness, so "has a sick child" would hold of anyone with
a child while anyone at all was sick — and an empty conjunction with it.
Class: neither label for the guard, no author's code having done what its author
believed; Refusal for the two shapes. Migration: bind the witness with a generator
antecedent and leave one literal under the unknown.
docs/naf.md, docs/aggregate.md
The strictest policy stops being the leakiest. Under :refuse a cross-context
functional or asymmetric clash was neither refused nor reported: the definitional
checks are scoped to the writer's own cone, the vantages are deliberately withheld under
that policy, and the exposure ledger had an entry kind for disjointness only.
settle/expose-constraint-clashes! files :functional and :asymmetric entries shaped
like the :disjoint one, re-deriving each clash from the vantages the refusal itself
would ask from — so the report cannot drift from what the door would refuse, and closing
the gap widened no vantage. It costs a KB declaring neither property two seqs, gated on
the declared vocabulary; a genlCx edge is the one trigger reaching past the region,
because visibility itself moves there. Entries are capped and never silently
(:constraint-exposure-truncated), and keyed on the handle pair so both arrival orders
file one entry. Class: Additive — a new entry kind in an accumulating ledger. A
:refuse KB that saw an empty violations may now see entries, which is the point.
docs/nmtms.md
A hidden set kept where the sentexes are, not rebuilt per placement.
res/excepted-handles fetched a record, re-derived a target and asked jtms/in? for
every stored except in the KB, per placement and per candidate justification.
kb/note-excepted! maintains {context -> {hidden-handle -> #{except-handle}}} at the
store instead, rebuilt by recover because no store holds it; the roster holds what is
stored and readers filter by belief, since an except can be defeated with no sentex
arriving. On 400 facts and 380 derivations the read goes from 88.8% of the run to 3.1% at
1,000 excepts, and 0 excepts is unmoved. res/hidden-fn hands back a predicate — nil when
the vantage hides nothing — so callers with handles in hand stop materializing a set.
The guard is an oracle: meta_sentex_test compares the roster against a full scan of
storage after every kind of arrival, defeat, revival and removal, across a recover.
Class: neither label; same arity, same contract, same answer set.
docs/contexts.md
The planner's subtype fan is made cheap rather than remembered. est-matches cost a
unary type literal over the type's whole subtype closure, once per pick per plan per
firing attempt — 13.2% of a chaining run on a 364-type hierarchy. For the shape that
costs, fan-of-roots reads the trie counts directly and the fan halves to 6.8%; a deeper
prefix still takes the general walk. Remembering the answer instead does not work and the
harness says so on both paths: a memo stamped on the change clock measures 0.98–0.99x
under chaining, the run's own placements retiring the entry between one plan and the next,
and a finer stamp is unsound rather than fiddly. lein perf gains visibility-reading,
the check that would have caught the walk above — flatness being a growth claim a ratio
can see. Class: neither label; the number returned is identical by construction.
docs/inference.md
ist places, and four layers had it half-reading. An (ist Ctx S) in antecedent or
exceptWhen position is refused :not-well-formed: the literal is matched under a
functor no sentex carries, so it satisfies nothing — while the naming check, range
restriction, canonicalization and well-formedness all read the frame as meaningful, so
check reported no problems and assert returned a handle. A positive antecedent yields
a rule that cannot fire; an exceptWhen never matches, so the conclusion it was written
to block stands believed; an (unknown (ist …)) fires unconditionally. The refusal names
both repairs. On the read side the same form now answers — every door taking a
sentence and a context asks S in Ctx, the named context winning over the argument, which
is the resolution assert already makes. It grants no visibility a context argument did
not already grant. A wrong-arity ist or one standing as a conjunct of a vector goal is
refused rather than answered empty. Class: Refusal for the antecedent and the two
read shapes, Additive for the reading. Migration: say a rule's premise with
(decontextualizedPredicate P) or a genlCx edge into the rule's own cone.
docs/contexts.md, docs/api.md
A datum that comes back believed goes back on the agenda. Two routes let belief
arrive with nothing chaining behind it, so the same knowledge in one order concluded and
in the other did not. A revived antecedent licenses the firing its defeat withheld — the
firing that never happened left no justification to release and reached no placement to
re-ask — so the trigger is read from jtms/revived, with jtms/touched-new naming the
nodes the window created so a re-seed is not a second forward chain per settle. The
equality door is the same defect where revived cannot see it: supersession moves belief
with no relabel behind it, so refresh-supersessions feeds *unmerged-sink* and settle
re-seeds, bounded by max-unmerge-rounds. Class: neither label; belief moves only
toward conclusions the same knowledge already reached in another order. Guards: both
un-merge routes and twenty orderings, six of which disagreed.
docs/nmtms.md, docs/equality.md
An answer picked from a fan is keyed on content, never on arrival. Fourteen reads
elected a survivor, a representative or a display line by retrieval order, which under the
columnar index is assertion order. Two of the keys were also being elided by an ambient
*print-length* — including a digest stored durably in termOfUnit content — so the
print vars are bound off wherever EDN is written for something other than a human to read.
The elections themselves span dedup-constant, clash reports, one-supporter, glosses,
rewrite-target, why-not's :contradicted-by, the quality rule line,
strongest-per-tuple, ASPIF emission and label-context's minted copies. And the handle
cache stopped answering from another KB: canon-stamp carries the record store, two KBs
declaring nothing symmetric having stamped one shared empty set. Class: neither label;
the order displaced was not reproducible for the same knowledge.
A collected NAT leaves none of its bookkeeping behind. nat/bookkeeping-handles
answered lazily while its caller retracts what it hands back, so a tail forced after the
termOfUnit map's own retraction found no expression and the result types stayed stored —
in the retrieval orders that hand back the map first, and not in the others. The set and
the orphan list are realized before the first retraction. Class: neither label.
A stored sentex is not a believed one, and five reads had it the wrong way round. ASP
grounding takes only believed assumption and constraint rules, a records-only import
stores each record with its dump strength and premise mark so a later recover has
premises to believe, the catalog's belief caveat probes for a believed datum rather than
any node, and the generator reports :stored as storage. The converse correction is the
reified-NAT sweep: uses count by storage, since a stored-but-OUT use revives and
collecting the map from under one dangles the constant. Class: neither label; each read
answers the question its docstring already claimed. docs/nat.md
Retrieval answers what the reference answers. Four matching reads disagreed with the
fan-out they are checked against, three of them silently: the mirror probe asks the
candidate's own functor rather than mirroring whenever some predicate under the queried
one is symmetric; naf-query unwraps an aggregate as it unwraps thereExists, so the
count moving no longer leaves the old conclusion believed; the rete alpha matcher skips
exceptWhen meta-sentexes; aggregate-values normalizes compound values, so two
spellings of one merged measure count once; and the three pre-canon reads that gated on
the list spelling take the vector spelling as the same sentence. Class: neither label;
the answer withdrawn was one the two paths disagreed about.
docs/inference.md, docs/canonicalization.md
A symmetric or inverse reading composes with what is derived. The mirror answered off
storage alone, so an inherited claim had no mirror; (pred b a) goes back through the
registry minus SymmetricProver now, bounded at two levels by *mirror-depth*. A partner
declared on a sub-predicate is the same edge (tax/inverses-under), the mirror licenses
the forward door and the firing names the symmetry it read through, and a defeat inside
arbitration re-joins what its sentence licensed over closures refreshed to what is
believed now. Class: neither label — answers are added, none withdrawn.
docs/inherit.md, docs/taxonomy.md
A negated rule is refused at the door. (not (implies …)) built a RuleSentex whose
key cannot be computed, so check answered admissible and assert threw a bare
IndexOutOfBoundsException from inside the store; connective-problems refuses it
:not-well-formed at both doors. And a rule cannot be stored inert: assert-inert is
the labeling primitive, and what one bought was a rule that had never been through
index-rule-sentex — believed, unreachable by any chainer, and unfixable by a later
assert of the same rule. Refused :not-indexable, with the message naming the other
inertness: set/inertRule is a rule that is believed, indexed, browsable and fires
neither way. Beside them, five legal API calls stop failing under clojure.spec
instrumentation. Class: Refusal for both — one shape reached a stack trace rather
than storage, the other a rule nothing was firing. Migration: assert a negated rule as
the positive rule with the negation in the consequent; a rule meant as documentation is
set/inertRule, and one already stored inert is retract!ed and re-asserted.
docs/solving.md, docs/inference.md
Storage keeps no dead frames, and a torn dump refuses. A round of durability fixes
across the disk store, the overlay and both import paths — the class the gate's memory
backend cannot see, which is why the matrix exists. :truncated-dump compares frames
read against what meta.edn states on both import paths, and the records-only path
refuses a dump naming a handle twice, where the second frame silently destroyed the first
record and counted both. No frame whose only fate is a tombstone: unmark-premise!
re-stores only a record carrying a strength, and the overlay's set insert removes a
removal record only when one exists. Two reads at open: validate-idx-tail! rides the
chunked walk instead of a seek per slot, and rebuild-premises! tombstones crash damage
only, rethrowing :unknown-frame — a build that cannot read a log must not delete it.
Index entries are normalized at the export frame, so one logical index stops dumping
byte-differently per backend. Class: Refusal for the two new kinds — one input is a
truncated file, the other loses a record it reports as loaded. Migration: re-export.
The uberjar loads the ontology it ships. Layer discovery listed a directory, which a
packaged jar need not carry, so an uberjar started with CxCore alone and no upper or
middle layer — silently, the KB simply being smaller than the same tree run from source.
Discovery lists the jar's own entries now, anchored on kb/CxCore.txt, and an unlistable
protocol is refused rather than answered nil. Class: neither label; a caller running
from source saw every layer already.
ASP routes to a solver that can actually run. AUTO handed off past the size cutoff
on available? alone, so a machine carrying libclingo and no clasp binary solved
small programs and threw on large ones — the failure arriving with the workload rather
than at the probe. The handoff is gated on a once-per-JVM probe that runs the binary; a
missing one is :solver-unavailable, clingo's aspif temp file is deleted on any exit,
and dense-roots' reserved family throws :reserved-family rather than pinning a
three-element decode for a four-element key. Class: Refusal for the two new :types.
Migration: install clasp if you relied on the large-program path, which was throwing.
The daemon answers a caller's mistake with 400, and the model's tool surface loses two
ops. :export's five destination refusals join serve/client-error-types, so a
directory that exists and is not empty stops counting as a backend fault at every reverse
proxy between the caller and the daemon; and :preview and :clear-caches are excluded
from the tool surface a model reaches, neither being a read whatever its name suggests.
A cancel can no longer unsettle a finished job. Class: Breaking — a status code, and
a removal from the exposed tool set. Migration: a client that retries on 5xx will report
these instead of retrying, which is the intent; call either op on the daemon or through
vaelii.core directly.
The browser writes what it shows, and shows what the ledger holds. Four ledger kinds
are about a pair or a budget rather than a dropped sentence, so each row printed "nil"
beside a live link to /term?q=nil, while :message at the top level went unread on the
two kinds that put it there — both renderers read either now, and core/violations states
that :sentence and :context are not on every entry. The editor survives a
conjunction-concluding line, which was refused :bad-handle after the write had landed;
a write is refused while an export walks the KB; unload! waits for a :cancelling
loader as for a running one; and a repeat source's key suffix is one past the highest
still loaded. A fork with an opts map naming neither :space nor :dir lands on its own
space rather than the shared process default, where two forks saw each other's writes.
A KB whose store cannot be counted renders :unreadable rather than as a healthy empty
one. Class: neither label. docs/overlay.md, docs/web.md
CLI flags mean what they say. assert-rule passes the --strength it parsed —
accepted-and-dropped stored a known-true rule at :default — and asserting a rule that is
already stored now marks the premise, which matters more: a generator's stamped rule is a
conclusion, so asserting it returned a handle for a rule that retracting the generator
took away. The class resolves from content, as :direction and :defeasible do. A value
flag refuses a following flag as its value instead of opening a directory literally named
--starter; a flag belongs to the commands that read it, and one carried elsewhere is
refused rather than dropped; and refusals print on stderr, so a script reading stdout
as EDN gets data. Class: Breaking for the stream move and the per-command roster,
Refusal for the flag-as-value. Migration: redirect with 2>&1 if you read refusals
off stdout; drop the flags your commands were ignoring; re-assert any rule whose
--strength was dropped. docs/canonicalization.md
Three more costs read the change rather than the KB. The overlay's removal record asks
the base kv-member? instead of materializing the whole posting per probe;
refresh-equality walks the moved handles through a reverse map instead of re-asking
belief of every supporter per merge; the qualitative join baselines live in their own
bounded map, so the resident cache clearing at its limit no longer degrades every later
delta join to a full one; and settle's clash-candidates sorts the moved region only
when something reads the order — a :refuse KB, the default, paid two sorts per settle to
feed a pass that was never going to run. core/check's docstring says what it predicts
and what it does not. Class: neither label; each answers what it answered.
What a write pays, and what an instrument can see. A run of costs that grew with what the
KB holds rather than with what the write touched — the taxonomy reconcile, the five
flat caches, the reified-NAT orphan sweep, a retraction's teardown, the standing-clash
ordering, a context-cycle repair, a repeated closure ask and a query plan's child count —
each now reads forward off the region a settle moved, and each has a lein perf check
standing where the claim is. Four places where arrival order decided an answer are
closed. Beside them the process gained instruments for the rest: the change feed crosses
the process boundary as a subscription with a cursor, long work is a job registry with a
screen that watches it, kb-quality reads the knowledge where every other instrument
reads the engine, and the conjunctive planner costs a join rather than a column of
literals.
No entry is Breaking, which is why this is a patch. Three carry a Migration line anyway, because a caller can observe them and should be told what to expect.
Triage, for a 0.5.0 caller. This is the index to what touches something you have written.
| If your code… | Then |
|---|---|
reads the first N of preview / edit-with-consequences!'s :believed-added or :believed-removed | you get a different N — the halves are content-ordered now, and were handle-ordered |
calls clear-caches and expects the literal cache's hit rate to zero | pass {:counters? true}; the reset is off by default |
walks a declared-transitive predicate that also declares an inverse | the walk sees the inverse-recorded hops too, so an ask can answer more |
branches on violations' :violation with a defaultless case | :arbitration-truncated is a new kind |
| builds on the shipped Space or Time vocabulary | an argument position that held thing now names a type, so an assert 0.5.0 accepted can meet an :arg-type refusal — widen the convicting declaration it names, or state the argument at a type the position admits |
A settle pays for the region it moved, not for what the KB holds. Eight reads were
charging the second. refresh-relation walked every supporter to decide whether to run
and recomputed every edge's believed-supporter set — 176.6 ms in a 64k-edge relation,
6.87x across 8x the edges the flip is not about — where :handle-edge, the transpose of
:support, reads the scope forward off the moved handles: 9.2 µs, 0.61x. The five flat
caches read :cache-support backward, so every settle paid the declared vocabulary to
learn it had nothing to do: 5.0 ms at 32k declarations to find nothing, 95 ms for a flip,
against 5 µs and 1 µs read forward off :cache-handle-keys. record-clashes! ordered
every standing clash report on the settle path, so an assert into a KB holding 800
dilemmas paid for a reading nobody asked for — stored in arrival order and ordered at the
read by settle/ranked, 1.60 → 1.07 ms against 1.05 before the ordering existed. A
genl edge with nothing above or below it cost 800 arbitrable-violations calls and a
genlCx edge re-derived 400 opposed bodies; both cost zero now, weighed per pair.
refresh-supersessions re-examined every displaced spelling every settle — 400 calls and
9.06x against 400 standing merges, now 0 and 1.70x — and a negated exception conjunct
registered under not, hiding the predicate it is about, waved the recheck through to
:all: 1,600 exception evaluations and 10.16x become 0 and 0.91x. What the scoping
removed was a whole-KB rescan four writers leaned on, so a writer touching a shared edge
records it in :dirty / :cache-dirty and the reconcile takes those whether or not
belief moved there. Gates: taxonomy-belief-flip, flat-cache-belief-flip,
standing-clash-reading, taxonomy-edge-arbitration, context-edge-arbitration.
Class: neither label; every reading answers what it answered.
docs/taxonomy.md, docs/equality.md
The arbitrating half of a bounded pass says when its budget stopped it. The reporting
half filed :exposure-truncated; the half that spends the same budget before anything
is decided said nothing, so a KB could leave standing a pair a finished sweep would have
defeated and show a clean ledger. :arbitration-truncated (:triggers :sample
:budget :message), one entry per settle; pairs past a cut go undecided rather than
decided the other way, and discovery accumulates in :clashes so a later settle can
surface them. Class: Additive — a new :violation kind.
docs/taxonomy.md
A disjointMetatype's membership is vocabulary, not a roster. (disjointMetatype M)
separates M's members by being consulted, so (M b_t) leaving stopped separating the
pair while the mark still stood, no closure moved, and neither member was in the region —
the KB kept reporting a dilemma the oracle does not. Only the departure was silent.
Four places where arrival order decided an answer. A predicate's second declared
inverse hid its first: the taxonomy held one partner per predicate, so
(transitive beforeEv) proved a chain with afterEv declared second and failed with it
declared first, and retracting one dropped the whole entry. :inverse is
{predicate -> #{partners}} now, maintained in both directions; inverse-of keeps its
shape and answers the lexicographically smallest. kb-quality ranked its capped lists
on the handle, so two loads of one KB reported the same :never-count over a different
:never — they rank on content now. A preview's capped diff was built off a region
sorted numerically, so the browser's 50-row panel showed a different fifty depending on
load order, with :bounded? true either way; both halves rank on sentence then context
at the point each caller caps. And an LLM prompt was cut before it was sorted, so a
term past the cap was shown a prefix of arrival order — the sort precedes the cut at all
four sites, the scan above it is sorted rather than left in the index's hash order, and
the heading tells the model it is looking at a sample. Migration: a caller reading the
first N of either preview half gets a different N — a different, better-defined N.
Class: neither label; nothing documented which of two declarations won, and a sequence
that moved with load order was never a contract.
docs/preview.md, docs/taxonomy.md,
docs/llm.md
A card's cut is a count, including the cut that was not counted. used-with claimed
everything its scan missed was still offered under a later tier, which is false for
exactly the predicates it exists to find; :dropped gains :unscanned, one O(1) read per
position. Beside it, correct.clj took first of a position's argIsa declarations, so
two contexts declaring one position decided by index order whether a reversed-argument
alternative was offered — it ranks by specificity now. A declaration's supporters were
being lost the same way: the disjointMetatype sweep walked believed memberships where
it records supporters, so a membership defeated at that moment could never be revived,
and derive-functional-equalities took first of a (functional P) stated in two
contexts, so retracting one withdrew a merge the other still licensed.
The model backends refuse by name, never by value, and a turn that ran out of tokens is
not a deletion. The JDK rejects a bad header character by quoting the value verbatim,
and the browser renders that onto the proposal panel — so a .env ending in CRLF was one
hop from putting an API key on a page; it is {:type :llm-bad-credential} carrying no
value. A streamed body reads under a watchdog (:llm-timeout), a 200 whose body is not
JSON raises :llm-bad-response with a bounded excerpt rather than escaping as the JSON
library's own exception, and eleven sites catch Throwable — a StackOverflowError on
deeply nested model text read as the model proposed nothing, canon overflowing around
500 nestings against the EDN reader's 5,000. :stop-reason is read before the diff, so a
truncated turn is :truncated rather than every row the model never reached coming back
as a :remove. apply-proposal! calls edit!, which is not a transaction, so it returns
{:result :applied :failed-at :error} and runs the settle by hand on the failure path.
The ^:llm consent gate follows the call graph to a fixpoint, one namespace having
defined a live-model that was a bare provider constructor. Class: Additive for the
three new :types.
The taxonomy's closures terminate, scope their repairs, and read a repeat. A genlCx
edge out of a context that sees another one back never returned: the depth potential ranks
the condensation, and raise-depth lifted a single node, which put it above its own mate
round the cycle without end — the lift moves whole components now. Retracting one edge of
a context cycle rebuilt every component (11.19x across 16x a background the retraction is
not about); an edge merely incident on a cycle is left alone, and the same retraction
reads 0.97x. TransitivePredicateProver holds the reach per
[direction predicate node context] stamped with the change clock — 0.10–0.14x on a
repeat over a 2,000- to 8,000-node chain, with no record read at all — and a closed goal
reads the cache without filling it. (P ?x ?x) cost a closure per node to answer nothing
and is one Tarjan condensation. And a transitive predicate's hops are the believed
matches, the inverse spelling among them: (transitive before) walked stored before
facts alone, so an (inverse before after) chain broke mid-walk and answered negative
with no diagnostic. Migration: such an ask returns at least what it did and never
less, so the only caller affected is one that counted on an inverse-recorded hop being
invisible. Class: Additive for the cache; neither label for the rest.
docs/taxonomy.md, docs/storage.md
A unary fact about a reified NAT was deleted with the constant, silently. One clause
of the orphan sweep matched on arity alone, so (prime (PrimeFn Seven)) — a claim
somebody asserted — made the constant look orphaned once its other uses went, and the
sweep retracted the claim with it. No error, no report. Bookkeeping is decided by
authorship now: nat/minted-for re-derives what mint-nat! wrote, and everything
else naming the constant is somebody's assertion whatever its arity. The sweep also cost
what the whole KB had ever reified — matching (termOfUnit ?k ?e) after every teardown
and to a fixpoint, 16.70x across 16x the NATs the retraction is not about, which on a
corpus of OpenCyc's order is seconds per retraction — and asks only about the constants
the teardown's own removals named: 1.22x, gated by retract-nat-scaling. A teardown
records only what that sweep will read, so a KB that reifies nothing pays nothing.
Class: neither label; a caller whose unary claim was being deleted underneath them was
not getting what they believed.
The change feed crosses the process boundary, as a subscription with a cursor. watch
takes a function and a function does not cross an EDN wire, so the daemon's half is
:watch / :poll / :unwatch / :watchers over a ring and an integer cursor — all four
in serve/feed-ops rather than serve/ops, which is what keeps a subscription out of the
model's tool set. A parked long poll held a thread nothing counted, so 55 concurrent polls
drove the 50-thread pool to 50/50 busy and /health from 62 ms to 25,997 ms:
max-parked (16) bounds how many may wait, and a poll that does not ask to wait is never
refused. {:wait-ms 1e300} answered 500 and ##NaN made the poll answer instantly
forever — it is nat-int?, capped before coercion — and a subscription dropped while it
was being registered took the whole feed down permanently. What one caller can allocate is
bounded three ways, nothing authenticating POST /op on the loopback default: 64
subscriptions, 256 events per ring, and one unpolled for five minutes reaped at the next
call. The ceilings bound the event count and not the bytes, and the docs say so.
Class: neither label — the feed is new in this release. docs/feed.md
A conjunction is costed as a join rather than as a column of literals. plan/est-rows
answers what est-matches does not — not can this literal fan out, a sound upper bound,
but how much, an expectation wrong in both directions and composing for exactly that
reason — returning the relation's shape and threading it through the planner's fold, so
the k-th pick is costed against the rows reaching it. No statistics table: every
number is already in the count-aware trie, and where neither side read a count the model
falls back on a proxy rather than calling the join a cartesian product. Generators are
split into connected blocks and ranked by adjacent transposition, with two placements
outside the law because they are claims an estimate cannot make. Planning one fixed
conjunction is now flat in the size of the KB — the cost model asked for a distinct-value
count once per literal per plan and (count (children …)) materialized the child set,
25x more against 32x the facts — and plan-scaling reads flat with p/count-children
against 24.6x without. lein bench-plan reports the q-error per join depth: 1.00 at every
depth through six literals, 1.00 / 2.75 / 2.87 on a corpus built to break the independence
assumption where a compounding model would read about 7.5 at depth 3, and 1.13x the best
of all 24 permutations against 2.18x before. query-plan carries :est-rows,
:est-prefix and :block beside :est-matches. Class: Additive; a new
IndexStore read, owed by both implementations.
docs/inference.md, docs/indexing.md
Long work is one mechanism: a job registry, and the screen that watches it.
vaelii.impl.jobs holds every operation that takes minutes rather than milliseconds, with
one status vocabulary, one progress reading and one cancel; /jobs watches them, a job
survives the request that started it, and nothing unsettled is ever dropped, since
forgetting a job releases its writer claim while a thread still running is still writing.
The catalog's load and export moved onto the registry rather than beside it, and
POST /chain became a job — a fixpoint over a corpus was minutes of this process's one
writer inside a request, with nothing on screen and no way to stop it. A second writing
job is refused :job-busy, naming the job that holds the writer. Class: not Breaking;
:busy was thrown at one impl site and read by one impl namespace.
docs/web.md
What this process is holding, on a page — caches, heap, and the profiler. Nothing
measured what the engine holds beside the store, which is a dozen derived structures
whose whole purpose is that a repeated question is not recomputed. caches is one read
over all of them — entries, the bound they are cleared at, what one entry counts, and the
hit rate where anything counts one. A row carries :scope and :counters because the two
differ: the literal cache's entries are one KB's and its counters are global. Every
cache-holding namespace declares itself into a register at load, so there is no central
list to forget, and a row that throws costs its own row and no other. The clear is a
measuring instrument rather than an edit — bare rather than !, moving no belief, usable
while a load runs — and it no longer zeroes the process-wide hit counters every other KB
in the JVM was reporting: that is {:counters? true}, off by default. Class: neither
label; both are new in this release. docs/web.md
The shipped ontology declares its positions, and decontextualizes predicate metadata
and nothing else. CxSociety declared (decontextualizedPredicate marriedTo) — the one
domain relation carrying a mark the rest of the ontology reserves for claims about a
predicate — so a marriage stated anywhere became a claim of the whole KB, and a rule
firing on the lifted copy put knows within reach of every data context. 227 argIsa /
argGenl declarations fill the positions that carried none, at thing throughout, and
six new upper types narrow them where a constraint should refuse something: Space takes
spatial_thing on all 100 of its positions, Time temporal_thing on 46 and time_point
on 16. Flight becomes a capability of a kind rather than a verb-shaped one-place
predicate, with the exception written twice because there are two things to except.
::prop-kind accepts every kind the engine marks, six of ten having been specced — so
:asymmetric, which has-prop?'s own docstring lists, was a documented call
instrumentation refused. Class: no label — ontology content (§3.8); what it owes is the
roster that pins the shipped set. Migration: a KB built on the shipped Space or Time
vocabulary can be refused where 0.5.0 accepted it — the refusal names its convicting
declaration. docs/argtypes.md, docs/contexts.md
A bulk load is decomposed, and the index write is 57% of it. lein bench-loadphase
loads one corpus repeatedly with one more phase stubbed out from the outside in, so
consecutive runs differ by one phase and the deltas sum to the baseline: at 1,000,000
distinct binary facts on :memory, 43.4 µs/fact and 23,100 facts/s, the index write is
56.8%, the JTMS node and premise mark 21.5%, the special-predicate suite 10.5%. Postings
are 35–39% of the load and count maintenance 6–10%, so the counts are priced and are not
the lever. Two write-side tricks measured worse and are reported rather than built. The
one write on that path that grew with the corpus is guarded — the negation memo's :dirty
set took a conj per fact — which does not move the wall clock but removes a structure
proportional to the corpus. lein bench-profile grows the two arms no reasoning workload
runs: an interactive arm whose read table is the inverse of every other's (88%
:term-index), and a churn arm, the only way unindex-sentex! runs at all.
docs/storage.md, docs/profile.md
Eleven checks join the perf gate, and 27 in the vector all judge. A read of the
standing clash set is Ω(n log n) by construction, so standing-clash-reading is calibrated
from both ends — 85.4x and 80.9x healthy over a floor near 66x, 937.8x with the read
filtering by cross product — and ships at 175x. retract-merge-scaling reads 5.66x alone
and 10.49x in place and ships at 18x: lein perf runs one JVM over the whole vector, so
that position dependence is a property of the harness rather than of the engine.
Operating the engine, in the two senses a running process needs: what it will let a caller do, and what it will tell an operator it is doing. The daemon authenticates and refuses to bind an address without a credential, ships as a container image, and says which posture it started in; every switch the build reads has a row in a table a test keeps honest; the log level is a dial a running process turns; and the failures that look like answers each gained something that says so. Nine entries are Breaking; the three Refusal entries (§3.8) cover input where what 0.4.0 did with it was run a configuration nobody asked for and report a clean pass.
Triage, for a 0.4.0 caller. Every Breaking and Refusal entry carries its own one-line Migration; this is the index to the ones that touch something you have written or deployed.
| If your code… | Then |
|---|---|
names :record-space / :index-space | one :space — keep the record number, drop the index one |
runs a daemon on a non-loopback --listen | export VAELII_API_TOKEN there and in every client, or it exits 2 |
reads a daemon 401, or branches on the wire :type | :unauthorized is a new one; GET /health is the only route without the token |
relies on VAELII_RETE=0 running the sweep | it means off now; unset, or =1 for on |
sets VAELII_NOHIER | it is VAELII_HIER, the other way up — VAELII_NOHIER=1 becomes VAELII_HIER=0 |
sets VAELII_QUERY_ENGINE / VAELII_QUERY_STRATEGY | a name outside the roster is refused rather than silently running the default |
sets VAELII_WEB_PORT for lein browser while -main stayed on 3000 | it moves both; pass --port 3000 to pin -main |
| lists KBs out of a search-path directory holding more than 200 | name the ones that matter in the catalog file |
| depends on vaelii and has no SLF4J provider of its own | add one — org.slf4j/slf4j-nop no longer arrives transitively |
branches on what term-role answers | :sense and :lexeme are two new answers — add arms, or a default |
writes a lex-namespaced predicate | it names a lexeme now, and a lexeme names no relation |
calls core/context-size, or sends the daemon :context-size | both are count-in-context — same arguments, same answer |
compares two compound terms with different | a merged symbol inside one now makes them equal, where it did not |
sets VAELII_ASP_SOLVER to a name outside clingo/clasp | it is refused at open-kb rather than silently running auto |
term-role
answers :sense for a disambiguated type and :lexeme for a symbol in the lex
namespace, so its documented domain gains two values a total case has no arm for; a
lowercase dashed name is a legal unary type where it was refused, and a lexeme applied to
arguments is refused (:lexeme-functor). Migration: add :sense and :lexeme arms or
a default; a lex-namespaced predicate names a lexeme now and cannot be applied.
docs/naming.md.context-size is count-in-context. The three O(1) cardinality readers are
one family and two of them said so. Migration: (v/context-size kb ctx) becomes
(v/count-in-context kb ctx) and {:op :context-size} becomes {:op :count-in-context}
— same arguments, same answer, old spellings gone rather than deprecated.
docs/api.md, docs/indexing.md.different descends into compound arguments. It normalized each argument
with one lookup in the symbol-keyed equality closure, so a compound was never found in it
and (different (QuantityFn 5 Kilogram) (QuantityFn 5 Kg)) answered different with
(sameAs Kilogram Kg) believed. Migration: a goal comparing two compounds can newly
answer false where a merge reaches inside one; comparing symbols is unchanged.
docs/equality.md.:space. A :disk KB's derived
directory is space-<n>, and the suite owns a block of two db numbers rather than four.
Migration: {:record-space 2 :index-space 3} becomes {:space 2}; either retired key
is refused by name (:unknown-option) rather than ignored, and :dir names a durable
directory the derived spelling does not reach.
Breaks: :record-space, :index-space
docs/storage.md.VAELII_API_TOKEN set every request carries Authorization: Bearer <token> or is
answered 401 {:type :unauthorized}, with GET /health the only route that answers
without it. --listen naming a non-loopback address without a token is one line on
stderr and exit 2, where 0.4.0 logged a warning and served the whole write block to
anything that could reach the port. Migration: export VAELII_API_TOKEN for a daemon
that names an address and give the same value to every client; nothing changes on the
loopback default. docs/operations.md.docs/operations.md gains a configuration table — 56 environment variables and system
properties, each with where it is read, its legal values, its default and the one thing it
decides. config_surface_test pins the names in both directions and checks each
file:line citation against the line it names, so the table cannot drift without a
failing test.=0 ran the sweep it names, and the two query switches took a bare
(keyword …), so a misspelt engine ran the default and reported a clean pass for a
configuration nothing exercised — the worst shape a test switch can have, since the result
reads as evidence. Migration: none for a value in the vocabulary; a job relying on =0
meaning on now gets the sweep off. docs/operations.md.VAELII_ASP_SOLVER matched no arm and ran auto, so a run pinned to clasp could
use clingo and report a clean pass; VAELII_CLINGO_MAX_BYTES threw from the first ASP
solve rather than from the configuration that was wrong. Both go through config/check!,
refused at open-kb by name. Migration: none for a legal value. docs/operations.md.VAELII_NOHIER is VAELII_HIER, and the sense is the other way up. A
switch carrying the negation in its own name makes =0 mean on, and the entry above had
just made the value load-bearing. VAELII_HIER defaults true. Migration:
VAELII_NOHIER=1 becomes VAELII_HIER=0; a VAELII_NOHIER left set is simply unread,
since a variable cannot be refused by name. docs/operations.md.set-log-level takes one of
:error :warn :info :debug :trace and installs Trove's console backend at it; log-level
reads back what is in force, and VAELII_LOG_LEVEL says it at startup. Unset, the engine
installs no backend at all, so an application holding its own *log-fn* keeps it.
Three :debug statements are what make turning it up worth doing. docs/operations.md.VAELII_WEB_PORT moves -main's port, and not only lein browser's. Both
read one default-port: the variable, else the vaelii.web.port property, else 3000, and
an explicit --port still wins. Migration: a deployment that set the variable for lein browser while relying on -main ignoring it now moves both; pass --port 3000 to pin it.sources is
recomputed per request, which is what lets a corpus appear with no restart and what made
the scan unbounded. The cut is named on the page and in the log, since a list that quietly
ends early reads as "this machine has no other KBs". Migration: name the ones that
matter in the catalog file to list them regardless of the count. docs/catalog.md.(isa Muffet Dog) breaks no naming invariant, so it stored a two-place relation nothing reads while
isa? answered false with nothing to search for. nm/advice reads intent where
problems reads the invariants, logging :warn once per process with the rewrite that
was meant; a :no-placement drop names genlCx beside the entry's own keys.
docs/naming.md.open-kb defaulting onto the shared in-RAM space now warns, naming both
fixes — give the KB its own number, or name {:space 0} to say the sharing is meant. A
warning rather than a refusal, since sharing the space is how recover sees the same
records and how a base is mounted. docs/storage.md.dispatch
reached into args with nth, so a short line answered error: IndexOutOfBoundsException and a long one dropped the extra operand in silence. One table
carries every command's arity, operands and gloss, so check-arity! and the usage text
cannot go out of step. Migration: none at the right arity; lein cli help prints the
count each command takes. docs/operations.md.docs/troubleshooting.md is a new page, indexed by symptom rather than by subsystem.
The engine's hardest failures are the ones where nothing goes wrong — a query answers
(), an assert returns a handle — so a reader has to already know the cause to find the
page explaining it. Nine symptoms, each with what you would have observed, how to confirm
it in one call, and the fix.lein lint gains a versions check, and the kondo row notes a local/CI mismatch. The
0.4.0 bump left the :with-foreign pin naming 0.3.0, so every
lein with-profile +with-foreign command failed to resolve; lint-versions reads that
pair and the lein-cloverage version stated twice.prove's docstring says it
counts proofs, not answers. A goal reachable both as a materialized fact and as the rule
concluding it comes back twice with equal maps — wrap in distinct, or reach for query
/ ask, which project to the goal's variables.Dockerfile and
docker-compose.yml. The container binds an address, so the token is required — an image
run without VAELII_API_TOKEN does not start rather than serving unauthenticated — and
one container per volume, a second opener being refused :disk-locked rather than scaled,
which is why the compose file carries no replicas:. docs/operations.md.reflect compiles src and bench and fails on
any reflection, auto-boxing or primitive-recur warning, and unused fails on a public
definition with no usage against a baseline. Ten warnings had to go first, none in src.org.slf4j/slf4j-nop no longer reaches a consumer's classpath. It sat in
top-level :dependencies, where it could win SLF4J's provider race against a consuming
application's own backend and silence it — the one thing a library must not do on a
consumer's behalf. Migration: an application that had no provider of its own and relied
on vaelii's now sees SLF4J's "no providers" line again — add one as its own dependency.
docs/operations.md.--listen bind with no VAELII_ALLOWED_HOSTS now warns. Naming an address
drops the Host allowlist to every Host answered — deliberate, since a reverse proxy
sets its own — but nothing said so at startup. host-posture names the policy beside the
token question, apart from the TLS line so a reader knows which check is missing.docs/troubleshooting.md and docs/storage.md name :type :unknown-backend.
open-kb throws it from five call sites and none carried a line in either doc; the entry
reads the other key each throw's ex-data carries to say which of the five it is.Correctness fixes found by reading the engine against its own stated invariants, in the places 0.2.0 and 0.3.0 did not reach: a backward-chaining loop guard that made a conjunctive query answer nothing, doors that disagreed about what they would accept, an index trusted without being checked against the records it describes, slots and keys that let arrival order decide belief, and derived caches a settle read one revival out of date. Thirteen entries are Breaking, which is why this is 0.4.0. The Refusal entries (§3.8) cover input where what 0.3.0 did with it was corrupt state or answer a different question in silence.
Triage, for a 0.3.0 caller. Every Breaking and Refusal entry carries its own one-line Migration; this is the index to the ones that touch source you have written.
| If your code… | Then |
|---|---|
hands assert text it did not read as EDN | it is refused (:shape) — fix the producer |
writes exceptWhen literals like (lives_in ?x cold_place) | spell them to the invariants; re-check any rule 0.3.0 left bare |
spells an edit! batch {:adds …} | spell it {:add […] :remove […]} — the old key wrote nothing |
names one of :record-space / :index-space | name both, or neither, in every opts map |
passes :direction to assert on a non-rule | it is refused; a rule takes it and now acts on it |
states one rule two ways (bare implies after a set/*Rule) | the slots join by content; retract! and re-assert to narrow one |
calls edit or edit-with-consequences | they are edit! and edit-with-consequences! — the wire op stays :edit |
matches :bad-opt, or a :shape from a non-map opts | match :unknown-option |
reads a dump's meta.edn dialect | it is :vaelii |
| stores skolem witness names across runs | the names moved; rebuild from the assertions rather than carrying both spellings |
| parses a daemon 500 for a client mistake | it is a 400 with a :type |
writes (ist Ctx S) with other than three elements | it is refused with :shape |
[(anc Tom ?y) (anc Tom ?z)] was empty where (anc Tom ?y) answered twice: the per-path
loop guard grew for a whole frame, and a queued conjunct is a sibling of the expansion
rather than a descendant. Silent in every direction — forward chaining and the node engine
both answered, provable? said false, and prove-within reported :status :complete.
docs/inference.md.assert refuses a sentence that is not an s-expression. A string — what a
failed EDN read hands back — was stored, indexed and believed as an object no query can
match; nil likewise; a symbol, number or map threw an untyped
UnsupportedOperationException. check refused all five, so the door built to predict
assert disagreed with it. Migration: nothing a working caller sent is refused; fix the
producer that handed assert unread text, and discriminate on :shape.exceptWhen query's literals are held to the naming invariants. A
literal docs/naming.md says is refused was stored as an exception no query could match,
so the rule read as guarded and fired as bare. Both doors read each conjunct before the
rule is stored. Migration: spell the exception's literals to the invariants, and
re-check any rule 0.3.0 left bare.edit! batch key nothing reads is refused. {:adds […]} bound nil, so
edit! wrote nothing and reported a success, while check-edit — whose job is to predict
exactly that — reported no problem; over the daemon it was a 200 {:ok true} for a write
that did not happen. Migration: spell the batch {:add […] :remove […]}.{:backend :memory :record-space 77} paired a private record store with
the process-default index every other in-memory KB writes: assert found the other KB's
handle, read it as a duplicate, stored nothing, and returned a handle in? answered
true for. Migration: name both or neither, in every opts map.layout.edn
gated the index's key shape and nothing gated its coverage, so a short index opened clean,
answered short forever and re-cemented its own stamp — and re-asserting a fact it could
not find minted a second handle for a sentence already stored. Three ways in: a torn
kv.log tail, a directory grown under a derived-index mode, and a crash between the
record write and the index batch. docs/storage.md.assert acts on :direction instead of accepting and dropping it. Only
assert-rule read the key, so a rule asserted {:direction :backward} stored :both and
forward-chained, materializing the cross product a backward-only rule exists to avoid. A
:direction on a non-rule, one contradicting the sentence's own wrapper, and a value
outside the roster are refused. Migration: spell the direction; a check caller matching
:shape for a non-map opts matches :unknown-option now.implies after a set/inertRule stayed inert and never fired. The resolution reaches
conclusions already derived, a justification baking the rule's contribution in at fire
time. Migration: the join only widens a slot; to narrow one, retract! and re-assert.
docs/canonicalization.md.clear-defeats! revived. A settle lifts
last settle's defeats at its top while the cached closures were refreshed only in
settle-finish — after constraint-nogoods had read them — so discovery asked its
question against a vocabulary one settle out of date, and a P/¬P pair made visible by
a revived genlCx edge went unarbitrated in a state recover disagrees with.apply-ops! read
@data before acquiring and published after releasing while compact! runs on the
durability daemon's executor — a thread the single-writer contract says nothing about — so
a compaction in either window rewrote the log from a map missing the in-flight write.
kv-clear! was sharper: a compaction between its truncate and its publish wrote the whole
pre-clear map back over the log just emptied.:type, not 500 with none. An
unreadable body, a wrong argument count and an unknown op all answered untyped, the first
two as 500s — which count as backend faults at every reverse proxy and 5xx alarm. The
engine's whole refusal vocabulary answers 400, unlogged. Migration: branch on :type
rather than on the status code; every {:ok false} carries a non-nil keyword./propose/* EDN read catches Throwable, as every other untrusted-EDN
read in the namespace already does: a deeply nested form raises StackOverflowError,
which an Exception catch let escape, and the browser has no exception middleware.query refuses a non-map opts and a negative or non-integer :max-depth.
Both read as "no depth", which is not an error condition but a different question — the
no-rule-expansion answer, returned as if it were the bounded one asked for.
{:max-depth 0} is admitted: it is that answer asked for by name. Migration: none for a
working caller.edit! refuses what check-edit reports, before applying anything. The two
disagreed in both directions: a 4-element :add entry applied with the extra silently
dropped where the dry run reported :shape, and a non-sequential entry threw a bare
ISeq error from every door. An unknown :remove handle is refused before any entry is
applied, so a checked-clean batch cannot half-apply. Migration: a remove-if-present batch
filters its handles through in? first.not- or ist-headed
consequent read its own frame as the predicate, so every frame-headed antecedent was "the
recursive literal" — two orderings of a negated-head rule minted two handles, and a
genuinely recursive rule with a negated head lost the hold-back, turning right-recursion
left-recursive.termOfUnit content, a handle in stored
content that order independence rules out. Migration: rebuild the KB from its assertions
(export! / import! replays firings) rather than carrying both spellings.
docs/skolem.md.edit is edit!, and edit-with-consequences is
edit-with-consequences!. The batch's :remove half runs the same retract-storage!
sweep retract! runs, while the name read as additive — the one gap in the ! roster the
convention exists to close. Migration: rename the calls; the wire op stays :edit.:bad-opt is retired, and one compression spelling survives. Two keywords
split one failure class on no rule a reader could predict — seven sites said :bad-opt
where thirty-four said :unknown-option. Migration: discriminate on :unknown-option
and :unsupported-compression.meta.edn names its dialect :vaelii. Decorative on the read
side, but it is a value in the frozen format and a documented key of import-dump's
return, so the name it carries is now-or-never. Migration: a reader matching the old
value matches :vaelii; import-dump reads dumps written either way.exceptWhen, can rewrite one goal to the same
canonical residual through the genl fan; keyed on the count the two children were one
key, so the second was dropped before it was enqueued and every answer only its exception
admits was lost — silently, on the path query routes to whenever :max-depth is given.
docs/inference.md.except queues the same re-check as its arrival. Only
the store and removal chokepoints called recheck-except, so an except defeated by a
settle's resolution revived nothing it hid: backward proving answered yes while the store
held nothing, and which belief set the KB ended with depended on the order the except and
its defeater arrived.recover reads only positive, atomic declarations into the taxonomy.
sentexes-with-functor returns both polarities and the rebuild arms destructure the
positive shape positionally, so a stored (not (genl a b)) bound its inner sentence as a
taxonomy node and nil as the other — poisoning every cache on any recover, the default
{:recover? :auto} reopen included.:neg nogood is an at-least-one in every reader. The ASP translation's soft branch
emitted only the positive body atoms, so a :neg-only nogood — what set/softConstraint
over negated choice literals produces — emitted its violation witness as an unconditional
fact: no steering pressure, and :violated reported a satisfied at-least-one as broken.
docs/solving.md.conflicts and contradictions are content-ordered. Each report's sides were already
ordered by content; the list came off a hash set of handle-keyed nogoods, so which pair
(first (contradictions kb)) returned was an answer about which was typed first.
docs/nmtms.md.implies at arity
2 threw a bare IndexOutOfBoundsException while arity 4 stored a silently truncated rule
check read as clean; (not A B) stored as a positive fact whose record and index
disagreed; a bare symbol passed as a rule literal was accepted, unmatchable; and a
non-finite measure magnitude stored cleanly, then threw out of every later duration goal
in the context. Migration: nothing a working caller sent is refused — every one of these
stored an object no query could match.find-terms and abduce take key rosters (a
misspelt :keep? tore down the scratch context whose handles the caller meant to commit),
the CLI refuses a flag outside its roster, escalate refuses a floor outside 0–7, and
import-dump refuses an unknown :framing where it guessed a reader and failed as a
ZipException. Migration: spell the key or flag as the refusal's roster lists it.vaelii.web --listen with no address parsed to a nil host — Jetty's wildcard bind, with
the Host allowlist reading nil as any — so a truncated command line put the browser's
unauthenticated write routes on every interface with the rebinding guard off. serve read
its positionals as a prefix, so a misplaced flag ran a disk daemon in memory.
Migration: none beyond completing the command line.assert, why, query and open-kb, and every other door took the misspelt key in
silence — answering a different question than the one asked. Migration: spell the key as
the refusal's roster lists it.lein cli assert '(dog Muffet)' Ctx --strength stored known-true content
at :default — and now exits 1 naming the flag; --memory --dir is refused as a
contradiction. Migration: none beyond completing the command line.Throwable, so a deeply nested form answers error: and a next prompt; the
browser's retract POST makes the check-edit round-trip docs/operations.md promises, so
a stale handle answers the problem panel rather than a success-styled "Retracted 0
sentexes".vaelii.disk.auto-compact=disabled read as compaction on, and
vaelii.disk.fsync=always as the three-second tick, the level the operator was trying to
leave. Migration: none for a working setup, but two spellings now act where they were
ignored. Spell what you mean. docs/storage.md.vaelii.index.snapshot on macOS and Linux only — docs/storage.md said so and nothing
enforced it, so on Windows the publish failed part-way through a four-file commit.
Migration: none — the property never worked where it is now refused.assert-rule refuses a rule literal whose predicate is a variable. Such a
rule was indexed under ?var0, which no arriving fact and no goal can spell, so it
answered no backward goal at all and fired forward only when the concrete-predicate
antecedent beside it arrived: two arrival orders, two answers, from a rule the engine
reported as accepted. An :inert rule is exempt. Migration: assert the instantiated
rules, one per predicate the metarule ranged over.Correctness fixes across the durable index, the snapshot, the JTMS, the export dump
and the bounded prover, a sweep that gives every refusal a :type, the one wire
contract 0.2.0's own sweep left qualified, and the serialization both servers' storage
layer already assumed. Then a run of inference and belief work: two orders that
reached two answers, the two doors that disagreed about an inherited claim, and two
enumerations that grew with the vocabulary rather than with their own answer. Eight
entries are marked Breaking — they refuse input 0.2.0 accepted or change an
observable contract, which is why this is 0.3.0 and not 0.2.1; the rest are compatible.
:type keywords are plain — :not-edn,
:cross-origin, :bad-host, :body-too-large, where the namespace serving them
qualified each one. This finishes tree-wide what 0.2.0's own breaking entry claimed.VAELII_MAX_BODY_BYTES override (16 MiB) live in vaelii.impl.guard, which both
read, so the browser answers 413 for an oversized form body where only the daemon
did. A daemon read is also fully realized inside the write monitor — wire-safe's
walk is what realizes a lazy answer, so running it after the monitor released let a
:query straddle a concurrent :assert.ex-info the engine throws carries a :type. Twenty refusals threw an
untyped map, so a caller had to guess from which keys were present. Two forms that
threw a raw Java exception now answer instead: (genl ?x ?x) / (disjoint ?x ?x)
answer the question one variable in both positions asks.ist form must have exactly three elements. 0.2.0 read assert and
check positionally, so (ist Ctx S junk) asserted with the extra silently ignored
and (ist Ctx) raised a raw IndexOutOfBoundsException. Both refuse with :shape.kv/index-layout-version is cleared, rebuilt from the records and restamped,
:recover? notwithstanding; without the gate such a log replays cleanly and then
misses every read whose key shape moved. A 0.2.0 durable store carries no stamp, so
its first open under 0.3.0 pays one automatic reindex: O(records), logged at :warn,
paid once. docs/storage.md.open-kb refuses a :base whose durable index is at an older key
layout (:stale-index-layout). The repair is a write and a base is mounted
read-only, so the refusal names the one place the rebuild can happen: open that
directory as a KB, then mount the fork over it.(fork (fork base)) is refused (:stacked-fork), which is what
docs/overlay.md has always stated.open-kb refuses a :recover? setting it does not name. :auto is the
default, true an alias for it, :warn and false the rest; any other value read as
the warn branch and handed back an empty TMS over a store that is not empty, which
answers [] to everything. A stale derived index is dropped on open whatever
:recover? says.close! releases a durable fork's own directory. A fork's writable half
takes the same exclusive lock as any durable KB, so without its own :dir it could
never be handed to another process short of exiting the JVM. 0.2.0's docstring promised
the opposite, so code that closed a fork in a finally and kept reading it worked and
now does not.<log>.compact behind, and the next compaction in the same
session opened that temp and appended to it — its replay then put back records deleted
in between. The cleanup is scoped to the pre-commit phase: past the marker the temps
are the only complete copy.open-kv-backend and open-token-log replayed their logs outside any guard, so a torn
frame propagated to a caller that answers a failed open by releasing the lock —
leaving it released while this JVM still held an open handle.kv-entries is realized under its monitor. Both halves were lazy,
so the seq handed back from inside the lock realized outside it. An export of a fork
taken while anything wrote it projected two states at once.HashMap racing its own rehash can leave a reader spinning on a probe loop that never
terminates. Its check-then-put is one step too, so two callers cannot leave the loser's
alpha permanently unmaintained.load-source claims the catalog under one monitor. The busy test, the
already-loaded test and the registration were three separate reads, so two requests
arriving together each passed all three and spawned a loader.Throwable, as the daemon does. A deeply
nested form overflows the reader's stack with a StackOverflowError, which an
Exception catch lets escape — a 500 where an unreadable term is the ordinary answer.roots-fallback.nippy carries argument-root postings, which are primary index truth,
and a missing or torn blob loaded as [] behind a warning while every argument-root
read answered #{} out of a snapshot that opened clean. The meta records the blob's
count and byte length, and the load thaws strictly..tmp until the
swap. A failed open likewise gives back the handles it took.export → import → export is
byte-stable. The provenance walk covers justification handles as well as sentex ones,
and import stores a justification's antecedents as a vector, the shape the engine's
own write path stores.:on-progress callback, with overlapping handle
spaces — could answer one KB's dedup question out of the other's supports. Keys coerce
fixnum boxing to Long at the boundary, since the map compares with Java equals
where the scan compares with =.prove-within prepares its goal, through the same prepare-goal-for-read every
other read path takes, so a reifiable NAT or a merge-retired spelling is the same
question under the bounded prover that it is under ask.genl sub-predicates at every arity,
as the reference res/match-pattern does. Fanning only for a two-element sentence gave
the opt-in matcher a different belief set on any rule whose antecedent had another
arity.place-conseq does not place a firing whose exceptWhen exception already holds, and
such a firing left no justification and nothing in jtms/blocked — so a settle pass
could not see it and the conclusion stayed suppressed after the block lifted. The same
knowledge in the other order concluded it. The refusal is recorded as [rule handle, bindings], capped at 4096 entries per rule. docs/exceptions.md.contradictions names the same side of a clash
whatever order the two arrived in; the two settle sweeps sharing one exposure-instance
budget walk their moved region in content order; query with {:proof? true :portfolio? true} returns each answer once; negation-nogoods writes with a
compare-and-set; and the node engine's inline join plans with the :est-override
belonging to its registry leaf.sentexes-matching and ask stop disagreeing about the same knowledge.
(argPreserving largerThan 1 genl) beside (largerThan dog cat) licenses
(largerThan chihuahua maine_coon), which ask reached while the fixpoint fired only
on the claims that were written — so the conclusion it never drew had no why, no
retraction path and no way to be an antecedent. The join contributes the handles the
inherited claim was read from, so retracting any of them withdraws the conclusion. One
asymmetry is left: a justification confers the weakest class it rests on, so a
:monotonic claim declared preserved by a :default declaration draws a :default
conclusion. docs/inherit.md.skolem/frontier-vars subtracts a post-join literal's output, so the Skolem NAT no
longer takes a variable into its argument list.disjoint goal is enumerated from the declarations rather than from the
vocabulary. A separation convicts two subtrees, so the answers are the subtypes of
what a visible declaration names and the cost is the answer's own size; 0.2.0 asked
taxonomy/disjoint? once per type, and once per pair with both arguments open. On
4,000 types carrying one separation that is 15.4 ms to 0.13 ms with an argument bound —
flat where it grew linearly — and at 1,000 types the two-variable goal goes from 2.5 s
to 4 ms. lein perf's disjoint-enumeration check is the claim.genlCx edge was answerable from exactly one of the two, and only when
that half was the one the settle moved. settle/clash-askers runs the check from the
candidate's own context and from the maximal common descendant of it and each context
holding a sentex it could pair with; nothing is widened.Not a drop-in upgrade from 0.1.0. Several of the changes below refuse input 0.1.0 accepted or change an observable contract — each such entry is marked Breaking — which is why this is 0.2.0 and not 0.1.1. Entries between here and the 0.1.0 header are in it, newest first.
[:argument-root pred pos term]), so a materialising join reads one literal's postings rather than
wading through every functor's at a shared slot. An [:argument-slot pos term] roster, reference-counted off those postings, keeps the
predicate-agnostic reads answerable as a union over the predicates present.
The packed long has no room for a fourth key part, so the dense roots route
the family to their boxed fallback. index-layout-version is 2: an index
written by 0.1.0 reads as :layout-changed and is rebuilt on first open —
no action needed, but a large durable store pays a reindex for it.:bad-handle) —
the vector assert returns for a conjunctive rule included, which 0.1.0's
retract! silently answered with {:removed-sentexes 0}. nil stays a
question with an answer (in? false, why {:stored? false},
add-provenance a no-op), and check-edit reports what edit! throws. why
also takes {:max-depth n} (default 256), marks a capped branch
{:truncated? true} instead of overflowing, and refuses bad opts
(:unknown-option).close! releases a durable KB's directory without waiting for JVM exit,
and import! is export!'s inverse. An unclean close still releases the
lock and registry; the first component failure is rethrown after.argIsa / interArgIsa / argGenl refusal names its convicting
declaration in content order, not in whichever order retrieval enumerated.assert refuses a non-map opts (:unknown-option) —
(assert kb s ctx :monotonic) stored a defeasible sentence in 0.1.0.
check already reported the same request; the two agree now.open-kb recovers by default (:recover? :auto). The old
:warn default handed back a KB that answered wrongly from a reopened
store. The cost moves to construction — O(records) on a populated store —
and {:recover? false} defers it. :warn and false remain.vaelii.core
plus thin shims vaelii.client, vaelii.starter, vaelii.web,
vaelii.serve and vaelii.cli over the impl namespaces they front. The
boundary is now what the docs said it was.vaelii.client's assert and assert-rule are spelled bare,
without the ! 0.1.0 gave them. A ! marks a fn that destroys stored
knowledge and neither does — both are additive, and retract! is what takes
one back — so the client now spells them exactly as vaelii.core does. A call
site writing c/assert! or c/assert-rule! no longer resolves.:type keywords are plain across the tree
(:unknown-source, not :vaelii.impl.catalog/unknown-source), and
open-kb's backend refusals carry one. Swept in the same pass: the settle
re-check queue no longer drops entries queued by a concurrent thread, and
foreign/register refuses with ex-info rather than an elidable {:pre}.:preserve-eval-meta needs it, and 2.9
ignores the key silently.POST /op requires Content-Type: application/edn. The type
is not CORS-simple, so a browser must preflight and the daemon answers no
CORS headers — which closes cross-site request forgery against a loopback
daemon. A client that sent no content-type is refused; add the header.VAELII_MAX_BODY_BYTES adjusts the cap.Host naming the interface the server was started on. A request with no
Host still passes (a non-browser client carries no ambient browser
context); a reverse proxy or local alias sets VAELII_ALLOWED_HOSTS.+with-foreign names a coordinate that exists
(com.vaelii/vaelii-foreign); the bare id it carried resolved nothing.The first release. What follows is the development log that produced it, newest first; every entry below is in 0.1.0.
(symmetric P),
(transitive P), (inverse P Q) and the argPreserving forms change what
may be concluded with no fact arriving; (functional P) sweeps the extent
when it lands.exceptWhen, unknown and census reads.why does.:ground-first by default; the goal-stack chainer drives one solution at a
time and level 7 streams its search.lein gate: lint, the suite, and the scaling claims, measured and failed on
rather than asserted; five checks added for costs that grow with what they
must not.<records>-<index>, all seven.argIsa entails as well as constrains, behind a toggle, retroactively too.lein browser; OpenCyc loading went from 378s to 277s.genl edges it subsumed through — belief and strength
run through them like any antecedent, checked against all 24 orderings.xz, an importer, and an oracle
comparing two knowledge bases; a dump lands every record at its handle.inherit declared rather than assumed; definitional checks reach every
term; argGenl constrains one level up.:memory-dense integer
postings, the :memory-columnar int-token trie with CSR compaction (3.18x
whole-index), and a bitmapped TMS behind a protocol.rewriteOf extended over predicates and types.ask-within normalizes its goal.exceptWhen canonicalized into the record, blocking excepted conclusions
with only reachable firings re-checked; its query reified the way a fact
is.different prover, a specification
suite, and wiring into assert; stratification is checked on edge change.assumptionRules with persistent solve and labeling contexts, proven on a
sudoku.exceptWhen began as a failing suite.core moved under vaelii.impl.*; ! reserved for
irreversible operations; tests became net-neutral, and a second concurrent
run fails fast rather than corrupting the first.The first day: a contextualized common-sense knowledge base with a trie index, inference and truth maintenance.
Can you improve this documentation?Edit on GitHub
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 |