A probabilistic, temporal, provenance-aware knowledge base on PostgreSQL. Statements are ground atoms with Beta-distributed belief, fuzzy temporal scope, per-context visibility, source attestations, and a full derivation graph. Designed as a substrate for an external reasoning layer: the database guarantees structural integrity, belief coherence, and provenance; the reasoner decides what to infer.
See CHANGELOG.md for version history.
- Design Principles
- Architecture Overview
- Installation
- Schema
- Predicates
- Objects & Statements Kernel
- Examples
- Known Limitations & Roadmap
- License
Eight invariants are load-bearing across the entire system.
1. Open-world assumption. Absence of a statement does not imply falsity. holds_at() in its default open_world mode returns only positively supported statements above a threshold. truth_status() distinguishes unknown (no claim made) from insufficient (weak, unopposed claims) from contradicted (actively opposed claims). The system makes no claim from silence and does not manufacture contradiction from mere weakness.
2. One atom = one row. A statement's identity is its content_hash — predicate, arguments, context, and temporal scope (belief is deliberately excluded). The hash is computed by trigger and is UNIQUE. Competing evidence about the same proposition merges into that row's Beta posterior via update_belief() and attestations; genuinely different claims (different value, different time window) remain distinct rows and are weighed against each other by the conflict machinery. ON CONFLICT (content_hash) DO NOTHING makes every seed file idempotent.
3. is_a / instance_of is the canonical type mechanism, scoped per context. Type membership is a derived materialized cache (type_membership, keyed per (object, type, context)) populated and retired automatically by trigger. Never assert type_membership rows directly. Transitive closure over subtype_of is resolved at enforcement time via recursive CTE, restricted to the statement's context chain (the context plus its ancestors) — a fiction-context assertion can neither overwrite nor contaminate the reality-context type system.
4. fuzzy_time is the canonical time representation. All temporal bounds live in the statement's t_start / t_end fuzzy_time fields. For ternary predicates like located_in(entity, place, time_period), the third arg is reserved for named semantic periods (e.g. victorian_era). When no named period is the semantic argument, use the no_period sentinel. Bare year-objects as args are a category error. year_to_jd() is calendar-exact (proleptic Gregorian).
5. Conflicts represent evidential opposition, not logical negation. direct_negation and value_conflict rows identify statements that are evidentially opposed — neither is the logical complement of the other. Open conflicts reduce effective_mean through a continuous competition model; resolving a conflict restores it exactly. Two competing positive statements coexist; the reasoner weighs them by belief.
6. One belief posterior per statement. A statement's own Beta and the pseudo-counts contributed by its (deduplicated) attestations fold into a single posterior. Every evidence channel — update_belief(), attestations, source credibility — moves the same number; none can silently mask another.
7. The derivation graph is live. Derivations consume the same belief pipeline that queries do — a premise's attestation-informed, conflict-suppressed effective belief — and forward_chained conclusions are recomputed automatically when a parent's belief or attestations change, down the provenance DAG. Derived rows are owned by this machinery: direct evidence about a conclusion belongs in attestations on the conclusion row (they fold into its posterior and survive recompute). Changes that move effective_mean without touching belief or attestations (newly detected conflicts, update_trust()) propagate on demand via recompute_derived_belief(); the stale_derived_belief integrity check reports any drift.
8. Kernel statements are correctable but protected from passive drift. The system_kernel source has is_protected = true. update_trust() requires p_override = true to modify kernel credibility, and update_belief() requires p_override = true for ontological statements. Kernel facts can be corrected by deliberate human action but will not drift from passive evidence accumulation.
common_knowledge_schema-v0_10.sql ← Tables, types, triggers, views, functions
↓
common_predicates_kernel-v0.10.sql ← 58 basis predicates across 13 groups
↓
common_objects_kernel-v0.10.sql ← Entity objects, type hierarchy, disjointness axioms
↓
examples-v0_10.sql ← Named individuals, facts, queries, conflict examples
The four files must be applied in this order. Each is idempotent: object and predicate inserts use ON CONFLICT … DO NOTHING / DO UPDATE, and statement inserts use ON CONFLICT (content_hash) DO NOTHING against the statement-identity index.
Prerequisites: PostgreSQL 15+ with pgcrypto, vector, and btree_gist extensions.
-- Create the database first, then apply in order:
-- createdb common_kb && psql -d common_kb
\i common_knowledge_schema-v0_10.sql
\i common_predicates_kernel-v0.10.sql
\i common_objects_kernel-v0.10.sql
\i examples-v0_10.sql -- optional; inserts named individuals and demonstration factsNo post-installation cleanup is required. (The transitional process ⊂ entity stub that earlier versions asked you to delete manually was removed from the schema seed in v0.8; the correct chain process ⊂ event_type ⊂ abstract ⊂ entity is established by the objects kernel.)
After installation, SELECT * FROM validate_kb_integrity(false); should return zero rows.
| Table | Purpose |
|---|---|
objects |
Unified namespace for every named entity, predicate, context, and source. Fine-grained typing via type_membership and subtype_of, not the kind column. Canonical names may not contain : (reserved as the stable_uuid delimiter). |
predicates |
Metadata for each predicate: arity, arg labels, type constraints, domain strictness, inverse predicate link, and is_functional (the leading args determine the final arg). |
statements |
Ground atoms with Beta belief distribution, fuzzy temporal scope, context, derivation metadata, statement kind, a UNIQUE content_hash identity, and (for derived rows) the combination_mode replayed on recompute. |
statement_args |
Normalised per-position argument store (authoritative; object_args / literal_args on statements are denormalised caches). |
attestations |
Links statements to sources with confidence weight. evidence_group_id prevents double-counting correlated sources. |
source_credibility |
Beta(α, β) credibility distribution per (source, context) pair. |
statement_dependencies |
Authoritative provenance graph: (parent_id → child_id) edges with rule name and weight. Used by why() and compute_derived_belief(). |
type_membership |
Derived cache only, keyed per (object, type, context). Populated and retired exclusively by trg_sync_type_membership (including on UPDATE and DELETE of the backing statement). Never write to it directly. |
conflicts |
Pairs of evidentially opposed statements with kind, severity, and resolution state. At most one open-or-resolved row per unordered pair and kind for direct_negation / value_conflict. |
contexts |
Tree of reasoning contexts (reality, domain, theory, fiction, hypothetical, game). context_chain() resolves a context's inheritance path. |
object_equivalence |
Probabilistic same-as links for entity resolution. |
content_hash is computed by the BEFORE validation trigger over:
predicate_id | object_args | literal_args | context_id | t_kind | t_start | t_end
and is UNIQUE (uq_stmt_content). Belief, interpretation, and derivation metadata are excluded: they describe our attitude toward the atom, not the atom itself. Consequences:
- Inserting the same atom twice raises a unique violation; seeds use
ON CONFLICT (content_hash) DO NOTHING. - To register disagreement about an existing atom, update its posterior (
update_belief(), attestations) — do not fork a duplicate row. - A claim with a different value or a different temporal window is a different atom; it coexists and is weighed by the conflict machinery.
validate_kb_integrity()recomputes hashes and reports drift (content_hash_drift).
Every statement carries a Beta distribution over its truth:
raw_mean = belief_alpha / (belief_alpha + belief_beta)
A statement with α=1000, β=0.001 is near-certain; α=3, β=2 is weak evidence; α=2, β=5 is a low-credibility counter-claim.
The posterior. Attestations contribute pseudo-counts, scaled by the source's credibility mean and kb_attestation_scale() (default 5.0 pseudo-observations per fully-confident attestation), after evidence-group deduplication:
posterior_alpha = belief_alpha + Σ w·scale·cred_mean
posterior_beta = belief_beta + Σ w·scale·(1 − cred_mean)
posterior_mean = posterior_alpha / (posterior_alpha + posterior_beta)
The competition model. For empirical statements, open conflicts (direct_negation and value_conflict) suppress belief continuously:
effective_mean = posterior_mean / (1 + Σ opponent_posterior_mean + Σ incompatibility_penalty)
This reduces exactly to posterior_mean when unopposed and is strictly monotone decreasing in opposition. Ontological and statistical statements bypass the competition model (effective_mean = raw_mean); rules have effective_mean = NULL.
All query functions (holds_at(), tell_about()) filter on effective_mean.
95% confidence intervals use the normal approximation to the Beta:
ci = raw_mean ± 1.96 × sqrt(α·β / ((α+β)² · (α+β+1)))
Each statement has a t_kind and optional t_start / t_end fuzzy timestamps:
t_kind |
Meaning |
|---|---|
eternal |
True outside of time (mathematical facts, definitions) |
always |
True for all of modelled time (strong but revisable) |
interval |
True during [t_start, t_end) |
point |
True at a single instant |
default |
True until explicitly contradicted |
Fuzzy timestamps are a composite type (best, lo, hi, granularity) in Julian Day Numbers, supporting uncertainty bounds at year, decade, century, or exact precision.
-- Encoding "circa 1828, ±1 year":
ROW(year_to_jd(1828), year_to_jd(1827), year_to_jd(1829), 'year')::fuzzy_timeyear_to_jd(y) returns the exact Julian Day of 00:00 UTC on 1 January of year y in the proleptic Gregorian calendar, so a year-granularity window labelled "1815" covers precisely the calendar year 1815.
Contexts form a tree (contexts.parent_id). context_chain(context_id) returns the context plus all its ancestors. Everything that resolves the type system — domain checks, is_a target enforcement, acyclicity, disjointness, incompatibility penalties, and the type_membership cache — operates within the asserting statement's context chain:
- A statement in a domain or fiction context sees the axioms of its ancestors (typically
reality). - Sibling contexts are mutually invisible: a game world's
is_acannot corrupt reality's cache, and reality's disjointness axioms do not leak into an explicitly counterfactual context unless that context inherits from reality.
Every statement carries a statement_kind that governs inference chain participation:
| Kind | Used for | Inference chain |
|---|---|---|
ontological |
subtype_of, disjoint_with, is_a (structural), equivalent_to |
May participate in logical chains |
empirical |
is_a (individual), has_role, located_in, etc. |
May participate in logical chains |
statistical |
typical_of, correlated_with |
Excluded from logical inference (hard exception); for probabilistic reasoning only |
rule |
implies with literal args; FOL inference schemas |
Opaque to holds_at() and tell_about(); consumed by reasoner |
statement_effective_belief — canonical belief view. Synthesises the Beta posterior (statement belief + attested pseudo-counts), the competition model, and incompatibility penalties into effective_mean. This is the primary input for all query functions.
SELECT id, raw_mean, posterior_mean, credibility_adjusted, effective_mean,
evidence_strength, open_conflict_count, conflict_severity
FROM statement_effective_belief
WHERE id = $1;posterior_mean is the unopposed posterior; credibility_adjusted is a diagnostic (mean source credibility of the attestations; NULL when unattested); evidence_strength counts direct plus attested pseudo-observations.
statement_belief — raw Beta statistics: belief_mean, ci_low, ci_high, variance, evidence_strength.
statement_credibility — source-credibility-weighted attestation quality, including the att_alpha / att_beta pseudo-counts that feed the posterior. Enforces the evidence grouping rule: each evidence_group_id contributes at most its single highest-weight attestation, preventing confidence inflation from correlated sources.
statement_view — human-readable join of statements with resolved predicate and arg names.
source_credibility_score — Beta statistics for each (source, context) pair.
holds_at(predicate_id, object_args, time, context_id, mode, p_threshold, p_min_evidence)
Temporal query. Returns statements that hold at a given time point.
-- Was Babbage at Cambridge in 1835?
SELECT * FROM holds_at(
stable_uuid('affiliated_with', 'predicate'),
ARRAY[stable_uuid('charles_babbage', 'entity'),
stable_uuid('university_of_cambridge', 'entity'),
stable_uuid('lucasian_professor', 'entity')],
'1835-06-01'::timestamptz,
stable_uuid('reality', 'context'),
'open_world', -- mode
0.5, -- p_threshold (filters on effective_mean)
2.0 -- p_min_evidence (requires real evidence, not just prior)
);Modes: open_world (default), default_true, evidence_weighted. Rule statements are excluded from all modes.
tell_about(entity_id, context_id, p_threshold, p_time)
Primary "what do we know about X?" interface. Returns all statements where the entity appears in any arg position, resolved to canonical names, ordered by effective_mean.
why(statement_id)
Returns the full provenance DAG for a derived statement, showing depth, rule applied, belief at each node, and effective belief.
truth_status(predicate_id, object_args, context_id, time, threshold)
Epistemic status of a proposition: 'supported' (a matching statement holds above threshold), 'contradicted' (matching statements exist, none clears the threshold, and at least one is party to an open direct_negation / value_conflict), 'insufficient' (statements exist, below threshold, no open opposition), or 'unknown' (no statements at all).
compute_derived_belief(parent_ids, chain_length, combination)
Computes (α, β) for a forward-chained statement from the premises of one rule application. Each parent contributes its effective pipeline belief — the same number queries see (attestation-informed posterior, suppressed by open conflicts) — so a contested or heavily-attested premise feeds its conclusions accordingly; rule-kind parents are licensing edges and are skipped from the math. A conclusion can never be more believable than its weakest premise, so both modes respect P(A ∧ B) ≤ min(P(A), P(B)):
'min'(default) — conclusion mean = min(parent means); exact for perfectly correlated premises.'noisy_and'— conclusion mean = Π(parent means); exact for independent premises.
Chain-length decay shrinks the result toward the uninformative 0.5 prior in log-odds space (logit(p) × 0.9^len), and the output evidence strength is propagated from the weakest parent (decayed per step, clamped to [2, 1000]) rather than reset. Statistical parents and unknown modes raise exceptions.
combine_evidence(parent_ids, p_check_independence DEFAULT true)
Merges multiple independent derivations of the same conclusion into one (α, β) by log-odds accumulation from a shared uniform prior — two independent 90% derivations correctly combine to ≈ 0.988. Evidence strengths are summed (clamped to 1000). Independence is verified by default: if any two parents share a provenance ancestor (or one is an ancestor of the other, per statement_ancestors()), the function raises, naming the pair and the shared ancestor, instead of silently double-counting. Pass p_check_independence => false only to override deliberately.
-- Per-derivation beliefs from compute_derived_belief(), then:
SELECT out_alpha, out_beta
FROM combine_evidence(ARRAY[$derivation_1, $derivation_2]);recompute_derived_belief(statement_id, p_write DEFAULT true)
Re-derives one forward_chained statement from its provenance edges: dependency edges are grouped per rule_name (each group = one rule application, recomputed with the statement's stored combination_mode); multiple groups are merged by log-odds when their parent sets share no provenance ancestor, otherwise the strongest single group wins (conservative — no double-counting). inverse_predicate edges are excluded (mirror rows are synced by equality, not re-derived). Writes only when the value changes; p_write => false is a dry run. Called automatically by the propagation triggers; call it manually after conflict detection or trust updates change a premise's effective_mean.
statement_ancestors(statement_id)
All provenance ancestors of a statement (itself included), walked through statement_dependencies. The substrate of the independence checks, and useful directly for impact analysis ("what feeds this conclusion?").
detect_conflicts(context_id)
Two passes; returns the total number of new conflicts inserted. Safe to call repeatedly (inserts are deduplicated against the unique pair index).
direct_negation— pairs with the same predicate and args whose 95% Beta CIs do not overlap. With statement identity in force, this catches differently-scoped assertions of the same proposition with incompatible beliefs.value_conflict— for predicates markedis_functional: pairs in the same context that agree on the leading args but disagree on the final value, with overlapping temporal scope and both positively believed (mean > 0.5). This catches real-world contradictions likehas_value(X, mass, 5kg)vshas_value(X, mass, 7kg). Supports all-object atoms and the canonical single-literal-value shape.
Both passes apply the temporal overlap guard (non-overlapping windows are not conflicting; eternal/always bypass).
context_chain(context_id) — the context plus its ancestors as uuid[]; the visibility set for type-system resolution.
update_belief(statement_id, weight, supports, p_override) — Bayesian conjugate update on a statement's (α, β). Requires p_override = true for ontological statements. Moves the posterior — and therefore effective_mean — regardless of whether the statement also has attestations.
update_trust(source_id, context_id, correct, weight, p_override) — Bayesian conjugate update on source credibility. Requires p_override = true for protected sources.
validate_kb_integrity(p_all) — integrity audit: statement_args sync, context-aware type_membership orphans, missing provenance, subtype_of cycles, sentinel violations, predicate–kind consistency, dependency cycles, disjointness_kind misuse, content_hash drift, and stale derived beliefs (dry-run recompute disagreement). Returns violating checks (or all checks with p_all = true).
| Trigger | Fires | Effect |
|---|---|---|
trg_a_validate_statement_args |
BEFORE INSERT OR UPDATE on statements | Enforces arity and literal-position rules; computes content_hash |
trg_b_enforce_kind_consistency |
BEFORE INSERT OR UPDATE on statements | Predicate ↔ statement_kind agreement; disjointness_kind policy |
trg_c_enforce_is_a_target |
BEFORE INSERT on statements | Blocks is_a into the role hierarchy (use has_role), resolved within the context chain |
trg_d_enforce_sentinel_usage |
BEFORE INSERT on statements | Sentinels cannot appear in structural predicates |
trg_e_enforce_subtype_acyclicity |
BEFORE INSERT on statements | Rejects subtype_of cycles within the context chain |
trg_sync_statement_args |
AFTER INSERT OR UPDATE on statements | Keeps the normalised statement_args table in sync |
trg_sync_type_membership |
AFTER INSERT OR UPDATE OR DELETE on statements | Maintains the per-context type_membership cache, including stale-row retirement on arg-change and delete |
trg_z_soft_domain_check |
AFTER INSERT OR UPDATE on statements | Hard domain violations raise; soft violations insert a (deduplicated) type_mismatch conflict |
trg_za_enforce_disjointness |
AFTER INSERT OR UPDATE on statements | Constraint-grade disjointness blocks typing, within the context chain |
trg_zz_auto_inverse_statement |
AFTER INSERT OR UPDATE on statements | Creates the inverse statement per temporal scope; propagates belief changes bidirectionally between mirror pairs |
trg_zzz_propagate_derived |
AFTER UPDATE on statements | Recomputes forward_chained children when a parent's belief changes; cascades to a fixed point down the DAG |
trg_propagate_attestation |
AFTER INSERT/UPDATE/DELETE on attestations | Recomputes forward_chained children when a parent's posterior changes via attestations |
58 basis predicates across 13 groups. All are idempotent (ON CONFLICT DO UPDATE).
| Group | Predicates |
|---|---|
| Taxonomic / type | is_a, subtype_of, has_property, same_as, different_from |
| Mereology | part_of, has_part, member_of, contains |
| Spatial | located_in, adjacent_to, origin_of, transferred_to |
| Temporal | before, after, during, simultaneous_with, has_duration |
| Causal / functional | causes, enables, prevents, used_for, capable_of, motivated_by |
| Agentive / social | agent_of, created_by, has_role, affiliated_with, related_to, opposite_of |
| Quantitative | has_quantity, greater_than, approximately_equal |
| Epistemic / modal | knows, believes, desires, possible, necessary |
| Linguistic | named, symbol_for, language_of |
| Event calculus | initiates, terminates, happens_at, holds_at_ec |
| Physical / lifecycle | made_of, has_state, precondition_of, affects |
| Inferential / correlational | implies, correlated_with, typical_of, occurs_in |
| Structural / logical | equivalent_to, disjoint_with, has_value |
Key design notes:
has_role(entity, role, scope) — ternary. Use no_scope as the scope arg when a role is genuinely unscoped. Do not use NULL or unknown.
located_in(entity, place, time_period) — ternary. Use no_period when no named period is the semantic argument. All temporal scope goes in t_start / t_end.
before ↔ after and part_of ↔ has_part have inverse_predicate_id wired. Inserting before(A, B) automatically inserts after(B, A) via trg_zz_auto_inverse_statement; updating either side of a mirror pair propagates to the other.
has_value(entity, attribute, value) is wired is_functional = true: the (entity, attribute) pair determines the value, so two believed claims with different values are a value_conflict. located_in, has_role, and the mereology predicates are deliberately not functional (they are one-to-many). Mark additional domain predicates functional only when "the leading args determine the last" genuinely holds.
correlated_with and typical_of — insert all statements using these predicates with statement_kind = 'statistical'. They encode population-level patterns, not individual-level logical claims.
disjoint_with — domain_strictness = 'hard'. Constraint-grade axioms block typing outright; incompatibility-grade axioms feed graded penalties into the competition model (matched against the asserted type and its supertypes).
The objects kernel seeds the full entity vocabulary, type hierarchy, disjointness lattice, FOL rules, and typicality prototypes.
entity
├── concrete
│ ├── living
│ │ └── animate
│ │ └── sapient
│ │ └── person ──────┬── mammal ── animal ── organism
│ ├── artifact ── physical_object └── agent ── institution ── government
│ └── place ── region / location
└── abstract
├── concept_type ── biological_taxon
├── proposition / information / knowledge_state
├── norm ── rule
├── role ── mathematician / programmer / inventor / scientist / …
├── symbol ── language / word / sentence
├── quantity ── number ── real_number ── integer ── natural_number
├── time ── time_interval / time_point
└── event_type ── process / action / change_event / state
The foundational abstract ⊥ concrete split is seeded explicitly at two levels so the domain trigger fires without requiring a running reasoner. Key graded disjointness pairs:
person ⊥ institution(belief ≈ 0.90 — sole-trader edge case)organism ⊥ artifact(belief ≈ 0.90 — GMO / synthetic biology edge case)truth_value ⊥ number(belief ≈ 0.70 — Boolean arithmetic overlap)
| Sentinel | Use |
|---|---|
no_scope |
Third arg of has_role and affiliated_with when the role is genuinely unscoped |
no_period |
Third arg of located_in (and similar ternary predicates) when no named time period is the semantic argument |
unknown |
Epistemic state: identity or value is not known. Not a scope or period placeholder |
Sentinels never acquire types and cannot appear in structural predicates.
subtype_of,disjoint_with,is_a(structural),opposite_of,different_from→statement_kind = 'ontological'typical_of→statement_kind = 'statistical'implieswith literal args (FOL inference rules) →statement_kind = 'rule'
Rule statements are opaque to holds_at() and tell_about(). They are queryable by kind and consumed by an external reasoning layer.
examples-v0_9.sql is an executable walkthrough in eleven parts (A–K): kernel inspection, simple queries, asserting individuals and facts, temporal queries, Bayesian belief and trust updates, derivation and provenance, live belief propagation through the derivation DAG and the combine_evidence() independence guard (F6/F7), conflict detection and resolution (including a functional value_conflict demonstration and the live competition model), aggregate views, enforcement demonstrations, integrity validation with the four-state truth_status(), and a Python (psycopg2) reference section.
A few representative patterns:
-- Assert a type (the trigger maintains type_membership per context):
INSERT INTO statements (predicate_id, object_args, belief_alpha, belief_beta,
statement_kind, interpretation, t_kind, context_id, derivation_type)
VALUES (stable_uuid('is_a', 'predicate'),
ARRAY[stable_uuid('ada_lovelace', 'entity'), stable_uuid('person', 'entity')],
999.0, 1.0, 'empirical', 'ontological', 'eternal',
stable_uuid('reality', 'context'), 'source_ingested')
ON CONFLICT (content_hash) DO NOTHING;
-- Fuzzy birth year, point statement at year granularity:
-- t_start = ROW(year_to_jd(1815), year_to_jd(1815), year_to_jd(1816), 'year')::fuzzy_time
-- Strengthen an existing claim (moves the posterior and effective_mean):
SELECT update_belief($statement_id, 0.9, true, false);
-- Find contradictions after bulk ingestion:
SELECT detect_conflicts(stable_uuid('reality', 'context'));
-- What is the epistemic status of a claim?
SELECT truth_status(stable_uuid('has_value', 'predicate'),
ARRAY[$entity, $attribute],
stable_uuid('reality', 'context'), NULL, 0.5);Belief mixes epistemic types within a kind. Ontological, empirical, and statistical uncertainty are all Beta distributions; the kind-stratified pipeline keeps them from interacting, but a formal epistemic typing of the parameters remains future work.
Conflict and trust changes propagate on demand, not reactively. Newly detected conflicts and update_trust() change a premise's effective_mean without touching its belief or attestations, so they do not trigger automatic recomputation of downstream conclusions. The stale_derived_belief integrity check reports any resulting drift; recompute_derived_belief() repairs it. Fully reactive propagation from the conflicts and source-credibility tables (with its fan-out cost) is a candidate for a future version.
Derivation reads go through statement_effective_belief. compute_derived_belief() and the propagation triggers read the canonical view per call. On very large KBs with deep cascades, prefer batching derivations (or materialising the view) over tight per-row loops.
Transitive closure is on-demand. Recursive CTEs resolve type closure at enforcement time, now with per-context filtering, which adds work on hot insert paths for deep hierarchies. Consider materialising closure per context chain if insert volume demands it.
The stable_uuid() function has a minor RFC 4122 variant-bit deviation. The variant octet is not set correctly. No practical consequence until strictly-validating external systems are involved.
Context inheritance is a tree, not a DAG. context_chain() follows single parents. Multi-parent contexts are deferred until a concrete use case drives them.
kb_attestation_scale() is a global constant. Per-source or per-context attestation weight requires editing the function. A configuration table is a candidate if tuning becomes routine.
MIT License
Copyright (c) 2026
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.