Skip to content

Latest commit

 

History

History
377 lines (276 loc) · 17.9 KB

File metadata and controls

377 lines (276 loc) · 17.9 KB

OpenOntology

LogicSRC OpenOntology is an open contract for durable, source-backed domain knowledge shared by humans and AI agents. Define the things in a domain, connect them with typed claims, preserve where each fact came from, and let agents query or propose changes through governed interfaces.

It is a standard, not a product. The normative contracts are JSON Schemas published under https://logicsrc.com/schemas/openontology/. @logicsrc/openontology is a reference implementation of those schemas — useful, but not the definition. Any storage engine, language, or model provider that satisfies the schemas and passes the conformance bundle conforms.

  • Storage-agnostic — SQLite locally, Turso/libSQL hosted, or your own adapter. No graph database required.
  • Model-provider-neutral — no LLM is needed to validate, query, or explain anything.
  • Usable offline — no account, no API key, no network.

Status: 0.1 Draft (OpenPRD 0001).

Five nouns

Everything in OpenOntology is one of five things. Learn these and you can read any package.

Noun What it is Example
Type What kind of thing something is Person, Project, Codebase
Entity A specific thing with a stable id eth:person:avery-lindqvist
Claim A typed statement about an entity, or between two AveryworksOnZK Prover
Source Where the claim came from a commit, a page, an API response
Change set A reviewable proposal to add, correct, merge, dispute, or retract "Add Alice to ZK Prover"

We say claim, not fact. A claim carries a status, a confidence, a time range, and its sources — so a reader can tell the current accepted view apart from something an agent proposed twenty minutes ago.

Quick start

No login, no hosted database, no model key, no network.

logicsrc ontology init my-ecosystem
logicsrc ontology validate my-ecosystem --strict
logicsrc ontology query run contributors --dir my-ecosystem --format table
logicsrc ontology query explain contributors --dir my-ecosystem --row 0

init writes a package that passes strict validation with no edits:

Created my-ecosystem/openontology.yaml
Created 3 entity types, 4 relationship types, 8 entities, 14 claims, 2 sources.
  ✓ 3 entity types
  ✓ 4 relationship types
  ✓ 8 entities
  ✓ 14 claims
  ✓ 2 sources
  ✓ 1 constraints
OpenOntology package is valid.

The four layers

Layer Contains Where it lives
Schema Entity types, properties, relationship types, constraints, saved queries, actions schema/
Knowledge Entities, claims, sources, evidence data/
Governance Change sets, reviews, approvals, policy, signatures, events runtime + changesets/
Runtime Queries, explanations, imports, governed actions, CLI/SDK/MCP/REST your process

The schema can always be fetched separately from the populated graph, and a package can always be exported without private runtime state or credentials.

Package layout

openontology/
  openontology.yaml        # identity, namespace, license, maintainers, file map
  schema/
    namespaces.yaml
    entity-types.yaml
    properties.yaml
    relationships.yaml
    constraints.yaml
    queries.yaml
  data/
    entities.ndjson
    claims.ndjson
    sources.ndjson
    evidence.ndjson

Small packages may inline arrays directly in the manifest. Large datasets should use newline-delimited JSON so implementations can stream validation and import.

YAML is an authoring convenience. Everything compiles to canonical JSON — keys sorted, undefined dropped, -0 normalized — before anything is hashed, signed, diffed, or published. A YAML-authored package and a JSON-authored package with the same model produce byte-identical digests.

Identifiers

Three forms are accepted, with one canonicalization rule between them:

compact   eth:person:alice              →  <namespace>person/alice
IRI       https://example.org/person/a  →  unchanged
URN       urn:logicsrc:person:alice     →  unchanged

Prefer the compact form: short, diffable, and stable when a package moves namespace. A Namespace object binds the prefix to the namespace IRI, which is what lets an exported IRI be read back as a compact id.

Ids never change when a display name or alias changes. A merge keeps the losing id forever as a redirect, so old references keep resolving.

Claims

A relationship is a claim whose object is an entity. A scalar property is a claim whose object is a typed value. Never both.

openontology: "0.1"
kind: Claim
id: "eth:claim:0042"
subject: "eth:person:avery-lindqvist"
predicate: worksOn
object:
  entity: "eth:project:zk-prover"
status: asserted
confidence: 0.94
validTime:                        # domain time: when it was true
  from: 2026-04-01T00:00:00Z
  to: null
assertedAt: 2026-07-25T19:43:12Z  # system time: when we recorded it
assertedBy: "agent:research-mapper"
runId: "run_01J3EXAMPLE"
sources: ["eth:source:commit-a41f"]
evidence: ["eth:evidence:007"]

Six statuses, each independently filterable in a query:

Status Meaning
asserted Part of the current accepted view
proposed Suggested, not yet accepted
disputed Contradicted by another claim or a reviewer
retracted Withdrawn; kept on the record
superseded Replaced by a later claim
derived Produced by a rule or query, with its inputs recorded

History is append-only. Claims are never edited in place. A dispute, retraction, or supersession appends a status transition, and the effective status is the latest one. The record of what was believed, and when, survives every correction.

Two clocks matter and are kept apart: valid time (when the statement was true in the world) and recorded time (when the system learned it). asOf queries the first; recordedAsOf queries the second.

Every claim must either cite a source or declare firstParty: true. Agent-authored claims must carry a runId. Derived claims must say what produced them. These are validation errors, not lint.

Querying

The portable query language is a JSON/YAML triple-pattern AST — not SPARQL, not Cypher, not a bespoke text parser. Database-specific languages are adapters over it.

openontologyQuery: "0.1"
match:
  - subject: "?person"
    predicate: worksOn
    object: "?project"
  - subject: "?project"
    predicate: investigates
    object: "eth:topic:zero-knowledge"
where:
  - variable: "?person"
    field: status
    operator: eq
    value: active
select: ["?person", "?project"]
include:
  claimStatus: [asserted]
asOf: 2026-07-26T00:00:00Z
limit: 100

Terms beginning with ? are variables; everything else is a constant. Multi-hop traversal is just more patterns. Supported operators: eq, neq, lt, lte, gt, gte, in, not-in, exists, not-exists, contains, starts-with, matches, before, after; plus distinct, orderBy, limit, offset.

Nothing is inferred. Transitivity, symmetry, and inverses apply only when the schema declares them and the query asks for them.

Implementations enforce server-side limits on depth, intermediate bindings, scanned claims, and row count. Exceeding one is an error, never a silent truncation.

Explanation

Every answer can be traced back to the claims that produced it:

logicsrc ontology query explain orgs-behind-a-network --dir . --row 0
### Why this row is present

Ontology: `ethereum-ecosystem@0.1.0`
Claim statuses included: asserted

1. `eth:l2:tessera` —deployedOn→ `eth:network:mainnet-sim`
   - status: asserted, confidence: 0.9
   - asserted by mailto:curator@example.org at 2026-07-26T00:00:00Z
   - source: Layer-2 registry <https://example.org/api/l2s>

Answer → claims → evidence → sources, every time.

Change sets

All writes go through a change set. It is the unit of atomicity, review, and audit.

openontology: "0.1"
kind: ChangeSet
id: "changeset:01J3EXAMPLE"
title: Add Alice as a contributor to the ZK prover project
rationale: Public repository activity and the team page identify the contribution.
createdBy: "agent:research-mapper"
runId: "run_01J3EXAMPLE"
operations:
  - op: add-entity
    value: { id: "eth:person:alice", type: Person, canonicalName: Alice }
  - op: assert-claim
    value:
      subject: "eth:person:alice"
      predicate: worksOn
      object: { entity: "eth:project:zk-prover" }
requiredApprovals: 1
status: proposed

Nine operations: add-entity, update-metadata, assert-claim, dispute-claim, retract-claim, supersede-claim, merge-entity, archive-entity, schema-migration. There is no delete.

A change set applies whole or not at all. Every operation is checked against the store first, so a batch with one bad operation leaves nothing behind. If it was authored against an older revision than the store's current one, it fails as a conflict — last-write-wins is never the default. Rollback is a new compensating change set, never a deletion.

Reviewers see semantic impact, not raw JSON:

Change set: Merge S. Haddad into Samir Haddad

+ 1 entities merged
! merging eth:person:s-haddad into eth:person:samir-haddad is reversible only via a compensating change set

Affected saved queries
  people-working-on-topic: result count 18 → 19

Approval policy
  1 approval(s) required

See OpenOntology governance for the review, approval, and policy model.

Safety model

The short version: an agent proposes, a human applies.

  • Agent-created change sets default to proposed. Agents cannot apply directly — not with every scope, not at high confidence, not in unattended mode.
  • Confidence is metadata, never permission.
  • --yolo and unattended execution cannot bypass a required approval.
  • Source text is data. An instruction embedded in an imported document cannot widen scopes, change policy, or move tool boundaries.
  • Model chain-of-thought is never stored. Inputs, outputs, evidence, policy decisions, and a short rationale are.
  • Every applied mutation records actor, client, request id, policy decision, approvals, and resulting events.

Validation

logicsrc ontology validate . --strict --format markdown

Four severities, reported separately: error, warning, info, policy. Only errors fail the run.

Checks cover JSON Schema structure, manifest integrity, referenced files, unique ids, id form, declared types, predicate domain and range, object datatypes, dangling references, temporal ordering, provenance completeness, agent run attribution, derivation inputs, excerpt limits and licensing, source staleness, and every declared constraint.

Constraints are deterministic — required predicates, cardinality, uniqueness, allowed values, domain/range, temporal bounds, and saved-query checks. No LLM decides conformance.

Every finding carries a stable code (OO-G-DOMAIN, OO-P-NO-SOURCE, OO-C-CARDINALITY, …), the object id, the file, the path, and a remediation hint where one is known. --strict promotes the "unknown type / unknown predicate / unnamespaced extension" family from warning to error.

CLI

logicsrc ontology init|validate|lint|build|inspect
logicsrc ontology entity   get|list|find|merge
logicsrc ontology claim    get|list|history|propose|assert|dispute|retract
logicsrc ontology query    run|explain|list
logicsrc ontology changeset list|create|diff|apply
logicsrc ontology import|export|audit|tui

logicsrc ontology tui renders keyboard-first panels — types, entities, claims, sources, queries, change sets, validation, audit — as plain strings that survive SSH, tmux, and a 60-column terminal. Claim status is shown as a glyph and the word, never colour alone.

Read commands take --format table|json|yaml|markdown|ndjson. Write commands produce a proposal by default. Exit codes are stable for CI: 0 ok, 1 validation failed, 2 usage error, 3 not found, 4 denied or approval required.

--as local|agent|reader selects the actor role. It cannot grant an agent apply rights; the policy layer denies those outright.

SDK

import { createOntologyEngine, loadOntologyPackage, localActor } from "@logicsrc/openontology";

const engine = createOntologyEngine({
  package: loadOntologyPackage("./ethereum-ecosystem"),
  actor: localActor("curator@example.org")
});

const result = engine.queryOntology("people-working-on-topic", { topic: "eth:topic:zero-knowledge" });
const why = engine.explainOntologyResult(result.id, 0);

const changeSet = engine.createOntologyChangeSet({
  title: "Add Alice to ZK Prover",
  operations: [{ op: "assert-claim", value: { /* … */ } }]
});
engine.approveOntologyChangeSet(changeSet.id);
engine.applyOntologyChangeSet(changeSet.id);

Storage, source adapters, query engine, identity, policy, events, and signatures are all injectable interfaces. The clock and id factory are injectable too, so a build, an applied change set, and a test run produce byte-identical output under both Node.js and Bun.

Storage

The store is an interface, so nothing about the model depends on where it lives.

import { createClient } from "@libsql/client";
import { createLibsqlStore, createOntologyEngine } from "@logicsrc/openontology";

const store = await createLibsqlStore({ client: createClient({ url }), seed: pkg });
const engine = createOntologyEngine({ store, actor });
// … apply a change set …
await store.flush();               // one transaction, then it is durable

The libSQL adapter hydrates the read model at open, serves reads synchronously, and buffers mutations as SQL that flush() writes in a single transaction. Callers that mutate must await it — the REST layer does after every applied change set. Everything persisted is append-only, so a crash before flush loses the last change set rather than corrupting history.

Migrations are versioned and idempotent. Indexes cover entity ids, types, aliases, external ids, subject, predicate, entity-valued object, status, valid time, recorded time, and sources; FTS5 backs label and alias search.

REST, SSE, and MCP

The reference service is described by OpenAPI at /api/ontologies/openapi and shares the published JSON Schemas rather than restating them.

GET  /api/ontologies
GET  /api/ontologies/{id}/manifest | /schema | /entities | /entities/{entityId}
GET  /api/ontologies/{id}/claims | /claims/{claimId}
POST /api/ontologies/{id}/query | /explain | /validate
GET  /api/ontologies/{id}/changesets            POST to propose
GET  /api/ontologies/{id}/changesets/{id}       POST .../review | /approve | /apply
GET  /api/ontologies/{id}/events                Accept: text/event-stream for SSE

No token is read-only. A curator token can apply; an agent token can propose and cannot apply. Mutating requests accept Idempotency-Key; a change set authored against a stale revision fails with 409 rather than overwriting.

The MCP server exposes the spec, manifest, schema, and saved queries as resources, plus tools for validate, get/find entities, query, explain, export, and propose. Write tools default to proposals, and applying is denied to agent actors by the same policy the SDK enforces — not by a separate rule that could drift.

Ingestion

Seven source adapters — CSV, JSON, YAML, NDJSON, Markdown, a generic JSON HTTP endpoint, and GitHub — turn foreign data into proposed change-set operations with sources and evidence selectors attached. Each declares what it can and cannot do, so "nothing was deleted upstream" is never confused with "this adapter cannot see deletions." See interoperability.

Interoperability

JSON Schema Draft 2020-12 is the canonical contract. JSON-LD 1.1 and RDF/Turtle are interoperability profiles, aliasing W3C PROV-O for provenance where the semantics genuinely match, and SHACL covers five of the seven constraint kinds. Exports report every field the target format cannot carry rather than dropping it silently. See OpenOntology interoperability.

Conformance

packages/schemas/fixtures/openontology/ ships a conformance bundle: conformance.json lists valid fixtures that must validate and invalid fixtures that must be rejected, for all sixteen normative object kinds. It depends only on the published schemas — a third-party implementation can run it without any LogicSRC code.

An implementation conforms to OpenOntology 0.1 when it:

  1. validates every valid fixture and rejects every invalid one;
  2. treats claims as append-only, computing current state from status and time;
  3. defaults agent writes to proposals and enforces approval policy on merges, bulk retractions, breaking migrations, and publishing;
  4. can trace any answer to claims, evidence, and sources;
  5. produces the canonical-JSON digest the bundle specifies for a given package.

What OpenOntology is not

  • Not one universal ontology for every domain.
  • Not a complete OWL reasoner, and not full RDF/SHACL/SPARQL.
  • Not a replacement for your system of record — it is a semantic and provenance layer over it.
  • Not a place where embeddings or model output are authoritative. Embeddings are optional, rebuildable, derived data; canonical facts are explicit claims.
  • Not the only open ontology engine, and not the invention of ontologies. Prior art in the semantic-web world long predates it, and OpenOntology maps to that work rather than replacing it.

Related