Koinii adjudication — the DEFAULT policy: leave-open-and-notify, plus the
lifecycle that keeps disputes from piling up, plus the two escalations (a named
arbiter's ruling, and a counted majority). A thin CLIENT
wrapper over the dispute reads (vaelii.koinii.dispute) — it touches no
engine internals and changes belief only through ordinary asserts / retracts.
koinii's first answer to a disagreement is NOT to pick a winner. When two agents
assert P and ¬P at :default, the KB stays paraconsistent — both coexist, argue
reports :contradiction (Priest's LP) — and this layer just records the dispute open,
pushes it to whoever is watching, and manages its life. Automatic resolution by source
trust is a harder, engine-side policy; do not reach for it here.
Three policies, one default (koinii.md, Adjudication: split by policy), and all three are here:
:monotonic assertion of the upheld side; its strength defeats the losing
:default side, so the clash clears, why explains who ruled, and retracting the
ruling reopens the dispute (cascading).Trust-resolve — automatic resolution by source trust — is out of scope for this layer: it is engine-side reputation work, and reaching for it here would resolve disagreements by weighing spoofable identities.
The dispute module owns the reads, the state vocabulary, and the dispute id; THIS module owns the policy
the dispute module deliberately left out — the clock, the timeout, and the notify sinks. The
clock is the engine clock (v/*clock*), so a lifecycle time and its assertion's
:created provenance agree.
Additive, like the sibling koinii modules: only the public core API — including
sort-by-content for the two content orders it reports (contested-premises,
standing-rulings) — plus koinii dispute, identity and types. Nothing in core loads it.
Koinii adjudication — the DEFAULT policy: leave-open-and-notify, plus the lifecycle that keeps disputes from piling up, plus the two escalations (a named arbiter's ruling, and a counted majority). A thin CLIENT wrapper over the dispute reads (`vaelii.koinii.dispute`) — it touches no engine internals and changes belief only through ordinary asserts / retracts. koinii's first answer to a disagreement is NOT to pick a winner. When two agents assert P and ¬P at `:default`, the KB stays **paraconsistent** — both coexist, `argue` reports `:contradiction` (Priest's LP) — and this layer just records the dispute open, pushes it to whoever is watching, and manages its life. Automatic resolution by source trust is a harder, engine-side policy; do not reach for it here. Three policies, one default (koinii.md, *Adjudication: split by policy*), and all three are here: - **Leave-open-and-notify** *(the default)* — record open, notify, change no belief. Correct for a ground truth people curate. - **Arbiter escalation** — a designated arbiter's ruling is an ordinary `:monotonic` assertion of the upheld side; its strength defeats the losing `:default` side, so the clash clears, `why` explains who ruled, and retracting the ruling reopens the dispute (cascading). - **Majority vote** — the ballots cast on the disputed claim are counted and the side with strictly more is upheld, through that same reversible ruling; a tie upholds nothing, so an evenly-split house stays open. **Trust-resolve** — automatic resolution by source trust — is out of scope for this layer: it is engine-side reputation work, and reaching for it here would resolve disagreements by weighing spoofable identities. The dispute module owns the reads, the state vocabulary, and the dispute id; THIS module owns the policy the dispute module deliberately left out — the **clock**, the **timeout**, and the **notify sinks**. The clock is the engine clock (`v/*clock*`), so a lifecycle time and its assertion's `:created` provenance agree. Additive, like the sibling koinii modules: only the public core API — including `sort-by-content` for the two content orders it reports (`contested-premises`, `standing-rulings`) — plus koinii `dispute`, `identity` and `types`. Nothing in core loads it.
Where a newly-opened dispute is pushed: (fn [dispute-entry] …), called once per
dispute as it transitions open -> notified. This is the extension point the channel's feed
subscribers and a configured human sink wire into; nil ships no push (the
lifecycle mark is still recorded, so nothing is lost — a later notify-disputes with a
sink still finds it un-pushed only if the mark was cleared). Idempotency does not live
here — it lives in the stored mark — so a sink need not dedupe.
Where a newly-opened dispute is pushed: `(fn [dispute-entry] …)`, called once per dispute as it transitions `open -> notified`. This is the extension point the channel's feed subscribers and a configured human sink wire into; nil ships no push (the lifecycle mark is still recorded, so nothing is lost — a later `notify-disputes` with a sink still finds it un-pushed only if the mark was cleared). Idempotency does not live here — it lives in the stored mark — so a sink need not dedupe.
The operator sink for sweep-stale: (fn [dispute-entry] …), called once per dispute
swept to :stale. An aged-out dispute is re-surfaced to a human, never silently
dropped. nil ships no push.
The operator sink for `sweep-stale`: `(fn [dispute-entry] …)`, called once per dispute swept to `:stale`. An aged-out dispute is re-surfaced to a human, never silently dropped. nil ships no push.
How long a dispute may stay live with no ruling before sweep-stale flags it (and
re-surfaces it to a human). A judgement call — a curated ground truth wants it long
enough that a real disagreement is not swept before anyone looks. Default 24h.
How long a dispute may stay live with no ruling before `sweep-stale` flags it (and re-surfaces it to a human). A judgement call — a curated ground truth wants it long enough that a real disagreement is not swept before anyone looks. Default 24h.
(cast-ballot kb principal stance claim-handle)Cast principal's stance (:for / :against) ballot on the claim at
claim-handle through identity/ingest: (votesFor id (sentexHandle claim)) in the
principal's own context, attested when principal is a :proof-tier one
authenticate minted. The in-process ballot resolve-by-majority counts;
channel/vote writes the same sentence unattested. An unknown stance is refused
(:koinii/no-such-stance). Returns the ballot's handle.
Cast `principal`'s `stance` (`:for` / `:against`) ballot on the claim at `claim-handle` through `identity/ingest`: `(votesFor id (sentexHandle claim))` in the principal's own context, attested when `principal` is a `:proof-tier` one `authenticate` minted. The in-process ballot `resolve-by-majority` counts; `channel/vote` writes the same sentence unattested. An unknown stance is refused (`:koinii/no-such-stance`). Returns the ballot's handle.
(contested-premises kb S ctx)The disputed premises the conclusion S rests on in ctx: the handles in S's support
closure that are a side of an open dispute ctx observes. Empty if S rests on nothing
contested. A pure read that changes no belief (unlike quarantine): an open dispute does
not block dependent reasoning, so a conclusion resting on a contested premise should be
visible as such.
Scoped to the ANCESTOR SET. S is every believed sentex of S in a context ctx
sees (v/sees?), the scoping disputes-in and ask? use: forward chaining places a
conclusion in the most specific context that sees its premises, an AGENT's context
rather than the channel's, so an exact-context lookup would answer 'nothing contested' for
a conclusion built on a disputed claim. Every visible derivation: where more than one
context holds S, their support closures are UNIONED, so no tie-break depends on seq
order or handles. Belief-filtered: a defeated stored sentex holds up no answer.
Content-ordered by the premise's [sentence context] (v/sort-by-content); the
context is required since the union can hold one sentence in two agents' contexts.
The disputed premises the conclusion `S` rests on in `ctx`: the handles in S's support closure that are a side of an open dispute `ctx` observes. Empty if S rests on nothing contested. A pure read that changes no belief (unlike `quarantine`): an open dispute does not block dependent reasoning, so a conclusion resting on a contested premise should be *visible as such*. **Scoped to the ANCESTOR SET.** `S` is every believed sentex of `S` in a context `ctx` sees (`v/sees?`), the scoping `disputes-in` and `ask?` use: forward chaining places a conclusion in the most specific context that sees its premises, an AGENT's context rather than the channel's, so an exact-context lookup would answer 'nothing contested' for a conclusion built on a disputed claim. **Every visible derivation**: where more than one context holds `S`, their support closures are UNIONED, so no tie-break depends on seq order or handles. **Belief-filtered**: a defeated stored sentex holds up no answer. **Content-ordered** by the premise's `[sentence context]` (`v/sort-by-content`); the context is required since the union can hold one sentence in two agents' contexts.
The principal recorded as ruler of a majority-vote resolution — not a real agent but
the stand-in 'the house majority', so who-ruled tells a counted vote apart from a named
arbiter's decree.
The principal recorded as ruler of a majority-vote resolution — not a real agent but the stand-in 'the house majority', so `who-ruled` tells a counted vote apart from a named arbiter's decree.
(notify-disputes kb channel)The default policy's core move: announce every not-yet-announced dispute channel
observes and record it notified. For each :open dispute (pending-disputes), record
a notified mark (open -> notified) and push the entry to *notify-sink*. Changes NO
belief: both sides stay in? at :default. Fires once per dispute: the stored mark
is the idempotency key, so a redelivery or a catch-up re-run does not re-push it. Returns
the disputes newly notified.
The default policy's core move: announce every not-yet-announced dispute `channel` observes and record it `notified`. For each `:open` dispute (`pending-disputes`), record a `notified` mark (`open -> notified`) and push the entry to `*notify-sink*`. Changes NO belief: both sides stay `in?` at `:default`. **Fires once per dispute**: the stored mark is the idempotency key, so a redelivery or a catch-up re-run does not re-push it. Returns the disputes newly notified.
(poll kb channel)One driver tick over channel: notify the fresh disputes, then sweep the aged ones.
Returns {:notified [...] :stale [...]}. A subscribe loop or a timer calls
this; the two halves are also usable apart.
One driver tick over `channel`: notify the fresh disputes, then sweep the aged ones.
Returns `{:notified [...] :stale [...]}`. A subscribe loop or a timer calls
this; the two halves are also usable apart.(quarantine kb channel target-handle)OPTIONAL, off by default: hide the contested sentex target-handle from channel's reads
and derivations, reversibly, via an index-layer except asserted in channel, for a
high-stakes channel that must never let a conclusion rest silently on a contested premise.
Scoped to channel: the claim stays believed in its OWN context. It OVER-SUPPRESSES:
channel can no longer see the dispute (argue there reads the lone surviving side), and
except interacts with the TMS and contexts. Prefer contested-premises /
rests-on-contested? unless a channel must exclude contested claims from derivation.
Returns the mask's handle; unquarantine! retracts it (cascading derivations back).
OPTIONAL, off by default: hide the contested sentex `target-handle` from `channel`'s reads and derivations, reversibly, via an index-layer `except` asserted in `channel`, for a high-stakes channel that must never let a conclusion rest silently on a contested premise. Scoped to `channel`: the claim stays believed in its OWN context. It OVER-SUPPRESSES: `channel` can no longer see the dispute (`argue` there reads the lone surviving side), and `except` interacts with the TMS and contexts. Prefer `contested-premises` / `rests-on-contested?` unless a channel must exclude contested claims from derivation. Returns the mask's handle; `unquarantine!` retracts it (cascading derivations back).
(quarantined kb channel)The sentex handles channel is currently quarantining — the targets of its
(except (sentexHandle ?h)) masks. 'What has this channel screened out' as a plain read.
The sentex handles `channel` is currently quarantining — the targets of its `(except (sentexHandle ?h))` masks. 'What has this channel screened out' as a plain read.
(resolve-by-majority kb id claim-handle channel)Resolve dispute id over the claim at claim-handle by MAJORITY VOTE, within channel.
Count the ballots (tally); if one side has strictly more, uphold it as a reversible
ruling (rule, arbiter majority-arbiter): the claim for a :for majority, its negation
for :against. A tie upholds nothing: the dispute stays open.
The count is the authority, every time: a ruling that no longer matches the count is
withdrawn before the new one lands, so a swing retracts the standing ruling and rules the
other side, and a majority that dissolves into a tie retracts it and leaves the dispute
open. Returns {:for n :against n :outcome :for/:against/:tie :ruling handle-or-nil :withdrawn [handle …]}. Idempotent: re-running on an unchanged count withdraws nothing,
so a driver may poll it.
Requires the :proof-tier identity policy, and a proven voter on every ballot. A
count by claimed voter name is spoofable, so this refuses (:koinii/identity-unverified)
under any other policy, and when any ballot on the claim, spoiled ones included, proves no
voter (proven-voter?), naming them in :unattested. cast-ballot under a minted
:proof-tier principal attests one, and a daemon with a token table records the
:principal of a wire ballot (docs/koinii.md, Identity and the write boundary). tally
is unrestricted.
Resolve dispute `id` over the claim at `claim-handle` by MAJORITY VOTE, within `channel`.
Count the ballots (`tally`); if one side has strictly more, uphold it as a reversible
ruling (`rule`, arbiter `majority-arbiter`): the claim for a `:for` majority, its negation
for `:against`. **A tie upholds nothing**: the dispute stays open.
**The count is the authority, every time**: a ruling that no longer matches the count is
withdrawn before the new one lands, so a swing retracts the standing ruling and rules the
other side, and a majority that dissolves into a tie retracts it and leaves the dispute
open. Returns `{:for n :against n :outcome :for/:against/:tie :ruling handle-or-nil
:withdrawn [handle …]}`. Idempotent: re-running on an unchanged count withdraws nothing,
so a driver may poll it.
**Requires the `:proof-tier` identity policy, and a proven voter on every ballot.** A
count by claimed voter name is spoofable, so this refuses (`:koinii/identity-unverified`)
under any other policy, and when any ballot on the claim, spoiled ones included, proves no
voter (`proven-voter?`), naming them in `:unattested`. `cast-ballot` under a minted
`:proof-tier` principal attests one, and a daemon with a token table records the
`:principal` of a wire ballot (docs/koinii.md, *Identity and the write boundary*). `tally`
is unrestricted.(rests-on-contested? kb S ctx)Does the conclusion S, as ctx sees it, rest on any premise that is currently disputed
there? The boolean over contested-premises — the flag a high-stakes reader checks before
trusting a derived answer, and it reads S up the genlCx ancestor set for exactly that reason: an
answer ask? gives at ctx off a premise stored below it is the case a safety flag exists
to catch, not the case it may miss.
Does the conclusion `S`, as `ctx` sees it, rest on any premise that is currently disputed there? The boolean over `contested-premises` — the flag a high-stakes reader checks before trusting a derived answer, and it reads `S` up the genlCx ancestor set for exactly that reason: an answer `ask?` gives at `ctx` off a premise stored below it is the case a safety flag exists to catch, not the case it may miss.
(rule kb arbiter id upheld channel)Arbiter escalation: arbiter (an agent id, e.g. AgentArbiter) rules dispute id within
channel for upheld, a side or its negation; any other is refused :koinii/not-a-side.
The ruling is an ordinary assertion: upheld at :monotonic strength in the arbiter's OWN
context (id/context-for), lifted under channel, with creator arbiter and tagged
:adjudication id in provenance. Monotonic strength defeats the losing :default side,
so the dispute reads :resolved and argue collapses to :true/:false.
One ruling per arbiter per dispute: a ruling for the other side is retracted first;
ruling the same side again is a belief no-op that returns the standing handle.
Reversible: retracting the returned handle reopens the dispute through the JTMS. The
open/notified/stale marks are cleared once the ruling has LANDED, so a reopen re-notifies;
a REFUSED ruling clears nothing. Returns the ruling handle. It resolves a :default
coexisting dilemma only: a :monotonic :conflict needs a human to retract a premise.
An arbiter who is a party to the dispute is refused (:arbiter-is-party): the
arbiter's context already holds one of the two sentences, so ruling their own way would
re-record their claim and ruling the other way would retract it, deleting the claim
instead of settling the dispute.
Arbiter escalation: `arbiter` (an agent id, e.g. `AgentArbiter`) rules dispute `id` within `channel` for `upheld`, a side or its negation; any other is refused `:koinii/not-a-side`. The ruling is an ordinary assertion: `upheld` at `:monotonic` strength in the arbiter's OWN context (`id/context-for`), lifted under `channel`, with creator `arbiter` and tagged `:adjudication id` in provenance. Monotonic strength defeats the losing `:default` side, so the dispute reads `:resolved` and `argue` collapses to `:true`/`:false`. **One ruling per arbiter per dispute**: a ruling for the *other* side is retracted first; ruling the same side again is a belief no-op that returns the standing handle. **Reversible**: retracting the returned handle reopens the dispute through the JTMS. The open/notified/stale marks are cleared once the ruling has LANDED, so a reopen re-notifies; a REFUSED ruling clears nothing. Returns the ruling handle. It resolves a `:default` coexisting dilemma only: a `:monotonic` `:conflict` needs a human to retract a premise. **An arbiter who is a party to the dispute is refused** (`:arbiter-is-party`): the arbiter's context already holds one of the two sentences, so ruling their own way would re-record their claim and ruling the other way would *retract* it, deleting the claim instead of settling the dispute.
(standing-rulings kb arbiter id)The rulings arbiter currently holds on dispute id: the handles in the arbiter's own
context whose provenance rule tagged with id. Believed rulings only
({:believed? true}), since rule's caller retracts what this returns and a defeated
ruling must not be withdrawn twice. Ranked by the ruling's sentence with
v/sort-by-content, not by handle (assertion order would make resolve-by-majority's
:withdrawn list a fact about how the KB was loaded) and not by a printed key (an ambient
*print-* var could elide two rulings to one key; a ruling of 9 precedes one of 10).
Canonical dedup gives one sentex per (sentence, context), so the sentence is a total
order.
The rulings `arbiter` currently holds on dispute `id`: the handles in the arbiter's own
context whose provenance `rule` tagged with `id`. **Believed rulings only**
(`{:believed? true}`), since `rule`'s caller retracts what this returns and a defeated
ruling must not be withdrawn twice. Ranked by the ruling's **sentence** with
`v/sort-by-content`, not by handle (assertion order would make `resolve-by-majority`'s
`:withdrawn` list a fact about how the KB was loaded) and not by a printed key (an ambient
`*print-*` var could elide two rulings to one key; a ruling of 9 precedes one of 10).
Canonical dedup gives one sentex per (sentence, context), so the sentence is a total
order.(sweep-stale kb channel)Sweep every dispute channel observes that has stayed live past *timeout-ms* with no
ruling to :stale, pushing each to *stale-sink*. A stale dispute is STILL live (both
sides coexist, argue still reports :contradiction); :stale flags that it aged out
unaddressed. Idempotent. Returns the disputes swept. One whole-KB scan for the
sweep: the skip reads the stored :stale mark (dispute/stale?), since every entry
disputes-in hands back is live and dispute-state would re-scan per entry.
Sweep every dispute `channel` observes that has stayed live past `*timeout-ms*` with no ruling to `:stale`, pushing each to `*stale-sink*`. A stale dispute is STILL live (both sides coexist, `argue` still reports `:contradiction`); `:stale` flags that it aged out unaddressed. Idempotent. Returns the disputes swept. **One whole-KB scan for the sweep**: the skip reads the stored `:stale` mark (`dispute/stale?`), since every entry `disputes-in` hands back is live and `dispute-state` would re-scan per entry.
(tally kb claim-handle)Count the ballots cast on the claim at claim-handle: {:for n :against n}, each a
count of DISTINCT voters (ballots).
A voter who cast BOTH stances (without retracting the first, against vote's contract)
has SPOILED their ballot — counted on neither side — so one self-contradicting voter can
neither manufacture a tie nor swing a majority; their confusion abstains rather than
double-voting.
Count the ballots cast on the claim at `claim-handle`: `{:for n :against n}`, each a
count of DISTINCT voters (`ballots`).
A voter who cast BOTH stances (without retracting the first, against `vote`'s contract)
has SPOILED their ballot — counted on neither side — so one self-contradicting voter can
neither manufacture a tie nor swing a majority; their confusion abstains rather than
double-voting.(unquarantine! kb handle)Undo a quarantine: retract the except mask at handle, so the claim and anything it
fed count again in the channel. Returns retract!'s counts.
Undo a `quarantine`: retract the `except` mask at `handle`, so the claim and anything it fed count again in the channel. Returns `retract!`'s counts.
(who-ruled kb ruling-handle)Read a ruling off its handle: {:arbiter :dispute-ids :at} from the provenance rule
recorded, or nil if ruling-handle is not an adjudication assertion. :dispute-ids is a
set: canonical dedup gives one sentex per (sentence, context), so an arbiter who rules
two disputes the same way records an adjudication on one handle twice, and a single-valued
tag would leave the earlier dispute with no standing ruling and let a second monotonic
ruling land beside the first (a :conflict no ruling can settle).
Read a ruling off its handle: `{:arbiter :dispute-ids :at}` from the provenance `rule`
recorded, or nil if `ruling-handle` is not an adjudication assertion. `:dispute-ids` is a
**set**: canonical dedup gives one sentex per (sentence, context), so an arbiter who rules
two disputes the same way records an adjudication on one handle twice, and a single-valued
tag would leave the earlier dispute with no standing ruling and let a second monotonic
ruling land beside the first (a `:conflict` no ruling can settle).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 |