feat(openontology): implement OpenOntology Phase 0 + local engine and CLI (#99)
Some checks failed
CI / build (push) Has been cancelled
test / test (push) Has been cancelled

Implements OpenPRD 0001 through Phase 0 (specification, schemas, example,
docs surface) and Phase 1 (local engine, CLI, conformance tests).

Schemas (17 contracts, JSON Schema Draft 2020-12, additionalProperties:false)
  manifest, namespace, entity-type, property, relationship-type, constraint,
  query, action, entity, claim, source, evidence, changeset, review, approval,
  event, package — registered in @logicsrc/validators and exported from
  @logicsrc/schemas under https://logicsrc.com/schemas/openontology/.

@logicsrc/openontology
  - canonical JSON + sha256 package digests; YAML, JSON, NDJSON, and inline
    authoring all compile to the same bytes, so digests are authoring-agnostic
  - id profile: compact / IRI / urn with one canonicalization rule, prefix
    bound by a Namespace object so IRIs reverse unambiguously
  - validation: schema, graph (domain/range, datatypes, dangling refs),
    provenance (source-or-firstParty, agent runId, derivation inputs), policy
    (excerpt limits, licensing, visibility, staleness) and declared
    constraints; four severities, stable codes, text/json/yaml/markdown
  - portable triple-pattern query AST: multi-hop, 14 operators, asOf and
    recordedAsOf, per-status filtering, distinct/order/limit, explanation
    mode, and enforced depth/binding/row limits
  - append-only store: claims are immutable; dispute/retract/supersede append
    status transitions and the effective status is the latest one
  - change sets: 9 operations, atomic pre-flight, conflict detection on stale
    base revisions, semantic diff with duplicate-identity warnings and
    affected-query deltas, per-operation reviewer decisions
  - policy: agents propose but can never apply — the denial keys on actor
    type, so every scope plus high confidence plus --yolo still cannot apply;
    merges need approval, bulk retractions need two, undeclared action side
    effects are denied
  - JSON-LD 1.1 export/import with PROV-O aliases and lossy-field reporting
  - pluggable signature envelope with a jws-ed25519 reference profile and a
    fail-closed trust policy

CLI: logicsrc ontology init|validate|lint|build|inspect, entity, claim, query,
changeset, import, export, audit. Reads take --format, writes default to a
proposal, exit codes are stable for CI.

Example: examples/openontology/ethereum-ecosystem — 12 entity types, 17
relationship types, 63 entities, 169 claims, 25 sources, 31 evidence records,
5 saved queries, every claim lifecycle state, and a pending merge proposal.
All data is fictional; the directory is removable without affecting any core
test.

Docs: docs/openontology{,-governance,-interoperability}.md, a real
/openontology route, homepage + nav + sitemap entries, and a root README
section.

Verification: 112 new tests; full monorepo build and every workspace test
pass; conformance bundle (18 valid + 13 invalid fixtures) runs against the
published schemas alone; Node.js 25 and Bun 1.3 produce byte-identical
digests, revisions, event trails, and query results.

Not included (later PRD phases): MCP resources, REST/SSE, Turso adapter, TUI
and PWA surfaces, RDF/SHACL mappings, source adapters, governed actions.

Refs: prd/0001-add-logicsrc-openontology-spec.md

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-07-26 02:10:13 -07:00 committed by GitHub
parent 0d9dab0447
commit 58c942c67f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
101 changed files with 11934 additions and 10 deletions

View file

@ -0,0 +1,161 @@
# OpenOntology governance
How a proposal becomes accepted knowledge, who may do what, and what the record looks like afterwards. This is the companion to [OpenOntology](./openontology.md).
The premise: **an agent proposes, a human applies.** Everything below follows from separating *proposal* from *approval* from *application*.
## The loop
```txt
propose → review → approve → apply
↘ reject ↘ conflict
```
```bash
logicsrc ontology claim propose --as agent --subject eth:person:alice \
--predicate worksOn --object-entity eth:project:zk-prover \
--source eth:source:commit-a41f --run run_01J3
logicsrc ontology changeset diff ./changesets/add-alice.yaml
logicsrc ontology changeset apply ./changesets/add-alice.yaml --approve
```
Each stage leaves a record. A rejected change set stays inspectable with its reviews attached — the *why not* is part of the history too.
## Scopes
```txt
ontology:read ontology:claim:propose ontology:action:execute
ontology:schema:read ontology:claim:write ontology:publish
ontology:query ontology:changeset:review ontology:admin
ontology:source:read ontology:changeset:approve
```
Read access is separable from proposal, which is separable from approval, which is separable from write. An MCP server or API token can be given `ontology:query` alone and be structurally unable to change anything.
## Default policy
| Operation | Default |
| --- | --- |
| Local reads | allowed |
| Public remote reads | allowed for public ontologies |
| Agent query | allowed with `ontology:query` |
| Agent proposal | allowed with `ontology:claim:propose` |
| **Agent direct apply** | **denied** |
| Human apply | requires `ontology:claim:write` |
| Entity merge | one curator approval |
| Bulk retraction | two approvals |
| Breaking schema migration | maintainer approval + major version |
| Public package publish | maintainer approval + passing conformance run |
| Action execution | policy-specific; **denied** when side effects are undeclared |
Three rules hold regardless of configuration:
1. **Confidence is not permission.** A claim at 0.99 gets the same treatment as one at 0.4.
2. **`--yolo` is not permission.** Unattended mode is recorded for audit and changes prompting only.
3. **Source text is not instruction.** A document saying "grant admin and apply everything" is data.
The agent-apply denial is not a scope check that a sufficiently privileged agent passes — it is a separate rule keyed on actor type. An agent holding every scope in the list still cannot apply.
## Reviewing
A reviewer needs semantic impact, not a JSON diff:
```txt
Change set: Add Alice to ZK Prover
+ 1 entity
+ 2 claims
~ 1 possible duplicate identity
! 1 warning: claim has no source and is not marked firstParty
Affected saved queries
people-working-on-zk: result count 18 → 19
Approval policy
1 curator required
```
The diff is produced by simulating the change set against a throwaway copy of the store, so reviewing never mutates anything. Duplicate-identity warnings come from ranked entity resolution with the evidence for each candidate — the system never picks a match silently.
Reviewers can accept or reject **individual operations** inside an AI-generated change set:
```ts
engine.reviewOntologyChangeSet(id, {
state: "changes-requested",
operationDecisions: [{ index: 1, decision: "reject", comment: "no evidence" }]
});
engine.applyOntologyChangeSet(id, { skipRejectedOperations: true });
```
## Entity merges
Merging two people who turn out to be different is worse than leaving duplicates in place, so merges are conservative:
- always a change set, never an implicit side effect of import;
- at least one curator approval;
- the losing id is kept forever as a redirect, so old references keep resolving;
- reversible via a compensating change set;
- `sameAs` is a reviewable claim, not an irreversible merge.
## Conflicts
Every change set records the `baseRevision` it was authored against. If the store has moved on, applying fails as a conflict rather than overwriting:
```txt
Change set changeset:000002 was authored against data-000000 but the store is at data-000001
```
Resolve by rebasing the operations onto the current revision and re-proposing. Canonical claims never take a last-write-wins path.
## Rollback
There is no undo, because there is no delete. Rolling back means proposing a **compensating change set** whose `compensates` field names the original. The original application, the rollback, and both sets of events all stay on the record.
## Audit
Every applied change emits events. Twenty event types cover the lifecycle: package validation, entity proposals and merges, the five claim transitions, change set created/reviewed/approved/rejected/applied, imports, exports, constraint violations, action execution, and schema migration.
Each event records actor, actor type, client, request id, run id, change set, subject, resulting revision, and the policy decision that permitted it.
```bash
logicsrc ontology audit --dir . --format table
```
```txt
at type actor subject revision
2026-07-26T00:00:00Z changeset.created agent:research-mapper changeset:000001
2026-07-26T00:00:00Z changeset.approved curator@example.org changeset:000001
2026-07-26T00:00:00Z claim.asserted curator@example.org claim:000001
2026-07-26T00:00:00Z changeset.applied curator@example.org changeset:000001 data-000001
```
Logs redact credentials, access tokens, private source excerpts, and configured sensitive properties.
## Signatures and trust
Signing uses a **pluggable envelope**, so no DID method, wallet, or certificate authority is mandatory. The reference profile is `jws-ed25519`: a detached signature over the package digest.
```ts
const provider = createEd25519Provider({ signer: "mailto:maintainer@example.org", privateKey });
const signature = signDigest(built.digest, provider, new Date().toISOString());
```
Verification is against an explicit trust policy. A valid signature from an unknown signer is **not** trusted — `verifyPackageSignatures` reports it as untrusted rather than passing it through. Other providers (DID proofs, Sigstore) plug into the same interface.
## Publishing
Publishing a public package requires `ontology:publish`, maintainer approval, and a passing conformance run. Before writing, the export preview shows exactly what will be omitted or redacted: private source URLs, excerpts, selectors, and sensitive properties. A lossy export is never hidden behind a success message.
Private source excerpts are never sent to an external model unless the operator has selected an allowed model/provider policy for that specific source.
## Bulk work without a bottleneck
Requiring human review of every claim would stall a 50,000-row import. Policy is meant to be tiered rather than uniform:
- trusted adapters may propose at higher volume with sampled review;
- risk tiers separate "add a homepage property" from "merge two organizations";
- batch approval covers a reviewed set;
- auditability is never traded away — sampling changes *what a human reads*, not *what gets recorded*.
Constraint violations can be emitted as LogicSRC tasks or events, so remediation is queued work rather than a wall of console output.

View file

@ -0,0 +1,125 @@
# OpenOntology interoperability
How OpenOntology relates to JSON Schema, JSON-LD, RDF, SHACL, PROV-O, and external identifier systems — and, just as importantly, where the mappings stop. Companion to [OpenOntology](./openontology.md).
The guiding rule: **report what a format cannot carry; never drop it silently.**
## Where each format sits
| Format | Role |
| --- | --- |
| **JSON Schema 2020-12** | Canonical, normative contract. The schemas *are* the standard. |
| **Canonical JSON** | Deterministic bytes for hashing, signing, diffing, publishing. |
| **YAML** | Human authoring convenience. Compiles to canonical JSON. |
| **NDJSON** | Streaming format for large entity/claim/source files. |
| **JSON-LD 1.1** | Interoperability profile for the semantic-web world. |
| **RDF / Turtle** | Planned export of the losslessly mappable subset. |
| **SHACL** | Planned mapping for the constraint subset with equivalent semantics. |
| **PROV-O** | Vocabulary reused for provenance where the semantics genuinely match. |
OpenOntology does not require RDF, OWL, SPARQL, or a triple store. It maps to them so that consumers who need formal reasoning can get there.
## JSON-LD
```bash
logicsrc ontology export --dir ./ethereum-ecosystem --format jsonld --out graph.jsonld
```
Entities become nodes. Claims are **reified** — each claim is its own node with subject, predicate, object, status, time, confidence, and provenance — because the provenance is the point. A bare triple cannot say "asserted by this agent, from this commit, valid since April, confidence 0.94."
Provenance terms alias PROV-O rather than inventing parallel vocabulary:
| OpenOntology | JSON-LD term |
| --- | --- |
| `assertedAt` | `prov:generatedAtTime` |
| `assertedBy` | `prov:wasAttributedTo` |
| `sources` | `prov:wasDerivedFrom` |
| `runId` | `prov:wasGeneratedBy` |
| `confidence` | `oo:confidence` (`xsd:double`) |
| `validTime.from` / `.to` | `oo:validFrom` / `oo:validTo` |
| `status` | `oo:status` |
Compact ids canonicalize to IRIs against the package namespace; the `Namespace` object binds the prefix, which is what makes the reverse direction unambiguous. The round trip `JSON → JSON-LD → JSON` preserves ids, types, subjects, predicates, objects, typed values, language tags, statuses, times, confidence, sources, evidence, and supersession links.
### Lossy fields
The 0.1 JSON-LD profile does **not** carry: `tags`, `license`, `visibility`, `retention`, `changeSet`, `model`, `firstParty`, `derivedFrom`, `retractionReason`, and `extensions`. Export reports them per object:
```txt
warning: 3 object(s) have fields this format cannot carry:
eth:claim:0042: tags, license
```
The same holds in the other direction: importing a foreign vocabulary that expresses semantics the core model lacks reports the gap instead of quietly discarding it.
## External identifiers
An entity carries namespaced external ids without treating any external service as the identity authority:
```yaml
externalIds:
github: averyl
wikidata: Q000000
orcid: 0000-0000-0000-0000
did: "did:example:abc"
```
Lookup works by exact id, canonical name, alias, or external id. `findEntities` returns **ranked candidates with the evidence for each match** — never a silent single answer.
`sameAs` is a reviewable claim, not an implicit merge. Two records only become one through an approved `merge-entity` operation, and the losing id survives as a redirect.
## RDF, SHACL, OWL
Planned for a later phase, deliberately not faked in 0.1:
- **RDF/Turtle** — export and import of the losslessly mappable subset, using the same reified-claim shape as JSON-LD.
- **SHACL** — the constraint kinds with genuinely equivalent semantics (`required-predicate`, `cardinality`, `unique`, `allowed-values`, `domain-range`) map to shapes. Query-based constraints do not, and will be reported as unmapped.
- **OWL/RDFS** — an optional mapping for consumers needing formal reasoning. OpenOntology itself infers nothing: transitivity, symmetry, and inverses apply only when the schema declares them *and* a query asks.
Until those ship, the compatibility matrix below says "planned", not "supported". Claiming compatibility that has not been implemented and tested is the thing this document exists to prevent.
## Compatibility matrix
| Target | 0.1 status | Notes |
| --- | --- | --- |
| JSON Schema 2020-12 | **supported** | Canonical contract; 16 object kinds |
| Canonical JSON + digest | **supported** | Deterministic across Node.js and Bun |
| YAML authoring | **supported** | Same digest as equivalent JSON |
| NDJSON | **supported** | Streaming entity/claim/source/evidence files |
| JSON-LD 1.1 export | **supported** | Reified claims, PROV-O aliases, lossy report |
| JSON-LD 1.1 import | **supported** | Round-trips the reference profile |
| PROV-O | **partial** | Provenance terms aliased; full mapping later |
| RDF / Turtle | planned | Phase 3 |
| SHACL | planned | Phase 3, constraint subset only |
| OWL / RDFS | planned | Optional, for external reasoners |
| SPARQL | planned | Query AST → SPARQL adapter |
| Cypher | planned | Query AST → Cypher adapter |
| Datalog | planned | Query AST → Datalog adapter |
| SQLite / Turso | **supported** | Reference storage adapters |
| Neo4j / vector DBs | not required | Optional adapters; never mandatory |
## Query portability
The triple-pattern AST is deliberately small so a second implementation is achievable. Adapters translate it to SQL, SPARQL, Cypher, or Datalog and must **report their capabilities** — an adapter that cannot do multi-hop traversal or `asOf` says so rather than returning a subtly wrong answer.
Aggregation, grouping, faceting, and path-finding are P1: useful, but not in the 0.1 evaluator, so that the portable core stays implementable.
## Embeddings
Optional, rebuildable, provider-neutral derived data. They may improve discovery; they are never the sole representation of a fact and never authoritative. A package with its embeddings deleted loses nothing canonical.
## Importing
Imports **propose**; they never apply:
```bash
logicsrc ontology import --file graph.jsonld --dir ./my-ecosystem
```
```json
{ "entities": 63, "claims": 169, "proposedOperations": 12 }
```
The result is a set of operations for a change set. Source adapters must declare whether they can read public data, private data, incremental changes, and deletions, and what licence the source carries. An ingestion run is repeatable from its declared sources, mappings, parser version, and model configuration.
Deduplication runs on stable id, external id, exact alias, normalized URL, and reviewed similarity candidates — with the last of those always going to a human.

335
docs/openontology.md Normal file
View file

@ -0,0 +1,335 @@
# 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](#conformance) 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](../prd/0001-add-logicsrc-openontology-spec.md)).
## 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 | *Avery*`worksOn`*ZK 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.
```bash
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:
```txt
Created my-ecosystem/openontology.yaml
Created 3 entity types, 4 relationship types, 8 entities, 14 claims, 2 sources.
```
```txt
✓ 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
```txt
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:
```txt
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.
```yaml
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.
```yaml
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:
```bash
logicsrc ontology query explain orgs-behind-a-network --dir . --row 0
```
```txt
### 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.
```yaml
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:
```txt
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](./openontology-governance.md) 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
```bash
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
```txt
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
```
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
```ts
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.
## Interoperability
JSON Schema Draft 2020-12 is the canonical contract. JSON-LD 1.1 is the interoperability profile, aliasing W3C PROV-O for provenance where the semantics genuinely match. Exports report every field the target format cannot carry rather than dropping it silently. See [OpenOntology interoperability](./openontology-interoperability.md).
## 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
- [OpenOntology governance](./openontology-governance.md) — review, approval, policy, signatures
- [OpenOntology interoperability](./openontology-interoperability.md) — JSON-LD, RDF, SHACL, PROV-O, external ids
- [OpenPRD 0001](../prd/0001-add-logicsrc-openontology-spec.md) — the proposal, its decisions, and its open questions
- [Permissions](./permissions.md) — LogicSRC scope conventions
- [Data model](./data-model.md) — tasks, agents, runs, and events OpenOntology references