DKE Library — Specification

Version: alpha · API 1 Status: Normative specification of the DKE library. Audience: Tenants who load facts into a DKE store and reason over them; authors of fact producers, whether they use our package or write their own. This document as Markdown: dke-library-spec.md — the same text this page is rendered from, for the producer authors above who would rather read the source than the page.


1. Status and scope

This specification is normative for the library — the code LangSyn publishes for you to use, as opposed to the language you write or the wire you send it over. Two artifacts are in scope, and they are two halves of one story:

Artifact What it is Where it is served
Reasoning modules (.dpy) DKE Python programs that supply the reasoning — a kind schema, standing rules, and queries /source/code/
dkepy (Python package) The package that writes the facts those modules reason over /source/dkepy/

A module supplies the reasoning; a producer supplies the facts. Neither is useful without the other, and the thing that binds them — the vocabulary they must agree on — belongs to neither and is specified in §5.

What is normative here and what is not. The Reasoning Modules Reference describes what the published modules contain and stands on its own authority as a description. This document states the same surface as a contract: what you may depend on, what may change under you, and what a conformant producer must do. Where the two disagree, this document governs.

This is the English edition. Every kind name, field name, rule name and query name below is the eng one. The library is a codebase like any other and its identifiers are written in a language; a Norwegian edition of this specification follows when the Norwegian edition of the library is translated, and until then no Norwegian names exist to specify. This is a separate question from the language parameter of the wire, which selects the keyword surface of the program you send and is specified in the DKE MCP Reference.

Out of scope. The DKE Python language itself (Language Specification); the wire protocol (DKE MCP Reference); the contents of your store, which are yours.

1.1 Conformance language

MUST / MUST NOT state a requirement; violating one is an error, though not always one a machine catches — §5.4 says exactly which are checked. SHOULD states a strong recommendation with a stated cost for departing. MAY states a permission. A conformant producer is any program that writes facts a published module reasons over, whether or not it uses dkepy.


2. The shared vocabulary

A reasoning module declares kinds and fields. A producer writes values into them. The names are the interface — there is no registration step, no handshake, and no negotiation: a fact lands where its name says it lands, or it lands somewhere nothing reads.

2.1 Naming

  • A kind name is scoped to its module: code.Function and finance.Function are different kinds and never read each other’s data.
  • Kind names are CapitalizedWords. Field names are lower_snake_case.
  • A producer MUST write the kind name exactly as the module declares it. Case and spelling are the whole of the matching; nothing corrects a near miss. §5.4 names the one check that catches this and the ones that do not.
  • A subject identifies one thing of that kind. Subjects are the producer’s to choose, MUST be stable across loads for the same thing, and MUST be used consistently: §3.3 gives the case where a display name in a subject position silently corrupts every answer.

2.2 Value classes

A field declares one of the writable value classes of the DKE Python language — string, int, bool, and the others given in the Language Specification §5. This document adds no value classes and no coercions. A producer SHOULD write the declared class; a mismatch is not caught by any library-level check (§5.4).

2.3 Three ways a field gets its value

This distinction is load-bearing throughout §3, and getting it wrong is the most common way a store produces confident wrong answers.

Written by Read by
Declared field you, or your producer rules and queries
Derived field the engine, kept current as its inputs change rules and queries
Rule conclusion the engine, in force exactly while its condition holds queries

A producer MUST NOT write a derived field or a rule conclusion. They are maintained for you and carry a derived provenance rather than a source, so a conclusion is always distinguishable from something you were told.

2.4 Absence is a value

Several rules in §3 read the absence of a field, not its contents. This is the sharpest edge in the library, so it is stated as a requirement rather than as advice:

A producer MUST write an absence-read field only where the thing it records is actually true, and MUST NOT write a placeholder, an empty string, or a default into one.

Writing such a field everywhere does not make a rule fire everywhere — it silences the rule everywhere, and the store then answers every question with the same list while nothing reports a fault. §3.3 names which fields these are. checkup distinguishes “nothing reaches these” from “nothing was looked for”; §4.4 says how.


3. Reasoning modules

3.1 What a module may declare

Four declaration forms, all of which become part of your store the moment you import the module:

Declaration Written by Normative note
Class the module Declares a kind and its fields.
Derived field the module A field whose value is an expression over other fields of the same kind.
Standing rule the module Ranges over exactly one kind and concludes one field on it.
Query the module A def you call by name, answering over the facts you have loaded.

A module MUST NOT contain anything else that persists. Module semantics in general — how import resolves, how namespacing works, what re-submitting does — are the language’s, in the DKE Python Reference §9.

A standing rule ranges over one kind. This is a property of the language, not a style choice, and it explains several fields in §3.3 that look redundant: a question about another kind cannot be a premise, so the observation is recorded on the side that needs it.

3.2 The published modules

One module is published: code, which reasons about a codebase. Its source is served at /source/code/ under its version, with a latest alias. §3.3 through §3.6 specify it.

code reasons about whatever codebase you load. It describes no particular one, and it ships no facts.

3.3 code — declared fields

A producer writes these. Fields marked absence-read are governed by §2.4.

Function

Field Class Notes
name string
module string A path. It hints at language at best; no query may rely on it for that.
lines int
branches int What counts as a branch is the producer’s decision, so two producers MAY disagree on scale.
loops int
deprecated bool Typically supplied by hand rather than by a producer.
language string Optional, absent-tolerant. What makes a polyglot store sliceable, and what makes two producers distinguishable.
test string Absence-read. A test seen to reach this function. Reaching is not executing: this is not a coverage measurement.
returns_ignored int Calls whose return value is not bound, tested, or handed onward.
called bool Absence-read. Recorded on the callee’s side, deliberately duplicating a Call, because a rule ranges over one kind (§3.1).
callers int Distinct callers, not call sites. Write it for every function including zero — it is read by comparison, not by absence.
callees int Distinct callees. Same rule as callers.
explained bool Absence-read. A reason was written down; see Rationale.

Classname: string, module: string, methods: int, language: string (optional, as on Function).

Callcaller: string, callee: string, site: string.

caller and callee MUST hold the subject of a Function, never its display name. The queries compare; they cannot tell a name from a subject. A display name here makes every function look uncalled, and nothing reports an error.

UnresolvedCallcaller: string (a Function subject), site: string, text: string (the callee as written).

A call whose target could not be named. No edge is asserted here, and a producer MUST NOT assert one. A missing edge is a known gap; a guessed edge is a wrong answer the queries cannot tell from a fact. Absent-tolerant: record none and everything else works, with dispatch_sites() returning nothing.

Rationalesubject: string (the Function or Class it explains), marker: string, text: string, site: string.

Stated, never inferred. Which comments count is the producer’s decision. Whatever convention is chosen, it SHOULD be one an author had to type on purpose: a convention wide enough to sweep ordinary comments turns every line into a fact and the query into noise.

Importimporter: string, imported: string, root: string.

Dependencyname: string, version: string, direct: bool, pinned: bool.

Counterpartname: string, left: string, right: string, relation: string, left_covers: int, right_covers: int.

Two functions that have to agree. Stated, never inferred — agreement is a claim about intent and no call graph can see it. relation names what is being counted, because it differs per pair; left_covers and right_covers count members of that set and are comparable to each other and to nothing else. This is the one kind no producer LangSyn publishes writes (§5.2).

3.4 code — derived fields

Maintained by the engine. A producer MUST NOT write one.

Field Definition
Function.complexity branches + loops + 1
Counterpart.gap left_covers - right_covers
Counterpart.reverse_gap right_covers - left_covers

Both directions of the gap are kept, each reading as a positive magnitude where it applies, because a query cannot negate a claim’s value.

3.5 code — standing rules

Each holds continuously: its conclusion is in force exactly while its condition holds over your data, and withdraws itself when the condition stops holding.

Rule Over Concludes While
hotspot Function hotspot complexity > 10
oversized Function oversized lines > 200
sprawling_class Class sprawling methods > 20
hub Function hub callers > 5
untested Function untested lines > 0 and test absent
uncalled_fn Function uncalled lines >= 0 and called absent
critical_untested Function critical_untested complexity > 10 and test absent
unexplained_critical Function unexplained_critical complexity > 10, test absent, explained absent
discards_result Function discards_result returns_ignored > 0
left_stricter Counterpart left_stricter gap > 0
right_stricter Counterpart right_stricter gap < 0

Two notes that are contract rather than commentary:

  • The thresholds are values, not percentiles. > 10, > 200, > 20, > 5 select whatever your store has, and select nothing when your store has nothing. A percentile would always name someone, including in a codebase with no hub at all. They are yours to argue with; they are not adaptive.
  • A rule cannot be all absences. untested and uncalled_fn carry a positive premise (lines > 0, lines >= 0) as a range, not a filter — a rule whose every premise is an absence has no set to work over and is refused.

3.6 code — queries

Called by name after import code. Every one answers over the facts you have loaded, and over nothing else.

Query Answers
callers_of(fn) Functions that call fn, directly.
callees_of(fn) Functions fn calls, directly.
impact_of(fn) Who breaks if fn changes: callers, and their callers, to depth 3.
importers_of(mod) Modules importing mod.
functions_in(lang) Functions recorded with language = lang.
hotspots_in(lang) Hotspots, narrowed to one language.
uncalled() Functions nothing was seen to call.
hubs() Functions many things depend on.
rationale_for(fn) Reasons recorded against fn.
unexplained() Critical functions carrying no recorded reason.
dispatch_sites() Calls whose target could not be named.
unused_dependencies() Declared dependencies nothing imports.
unpinned_dependencies() Dependencies with no pin.
asymmetries() Counterpart pairs where one side checks more.
discarded_results() Functions throwing a result away.
critical() Complicated and unreached.

impact_of walks to a fixed depth, not to a fixpoint. That is a stated bound, not an approximation to be improved: the language guarantees termination and a reachability closure is not something it will run.

An empty answer is an answer. It means no loaded fact satisfies the question — which is different from the question being unanswerable, and different again from the field being empty because nobody wrote it. §4.4.


4. dkepy

The Python package a customer installs to write facts. Distribution name dkepy; import name dke. They differ because dke is taken on PyPI, and neither may change casually — both appear in customer code.

dkepy is not required. It is one conformant producer, published so that you have a worked example and do not have to write the boring half yourself. A producer in any language that satisfies §5 is equally conformant.

4.1 What the package guarantees about itself

These are properties, asserted in the package’s own suite, that a caller may depend on:

  • No network. No module in the package opens a connection, except dke.client, whose entire purpose is to.
  • No subprocess. The package spawns nothing.
  • Zero runtime dependencies. Nothing beyond the Python standard library.
  • Python 3.10 or later, a hard floor with a named cause.
  • No engine code. The package contains none and never will.

4.2 dke.factwriter — domain-free

The layer that knows how to write a fact-loading program and nothing about what the facts mean. Public surface:

Name Contract
sanitize(name) -> str Make a name safe to use as an identifier.
Ids Stable subject allocation. get(qual) returns the same subject for the same qualified name, for the life of the instance.
FactProgram Builds one DKE Python program. comment, open, q, put, table, load_marker, render.

FactProgram.put(attr, value) is the one way a target cell is named, and its argument is the Kind.subject.field path of §2.1. A producer that writes through anything else is outside what §5.4’s check can see.

dke.factwriter is re-exported at the package root: from dke import FactProgram, Ids, sanitize.

4.3 dke.ingest — the code readers

Readers for one domain — source code — that are both consumers of dke.factwriter and the worked example of §5. dke.ingest.core holds the domain-shared layer and MODULE = "code", which names the module these facts are for, and one reader submodule per source language sits beside it.

Naming asymmetry, stated because it will otherwise look like a bug. The reader class for code.Class is spelled Klass in Python, because class is a Python keyword. The fact it writes is code.Class. The Python spelling is the producer’s business; the kind name is the contract.

Exit statuses are part of the contract: EXIT_OK, EXIT_NO_SUCH_PATH, EXIT_NOTHING_TO_READ. A reader that read nothing MUST NOT exit zero — an empty load and a successful one are the two outcomes it exists to distinguish.

4.4 dke.client — reaching the service

The wire client. DEFAULT_ENDPOINT and PROTOCOL_VERSION name what it talks to. Its error hierarchy is a contract: DkeError is the base, TransportError carries an HTTP status, ProtocolError carries a wire code, and PaymentRequired is a ProtocolError a caller SHOULD handle distinctly.

On telling an empty answer from an empty store. When a query returns nothing and you cannot tell which it is, checkup reports unmet <Kind>.<field> for a cell a standing rule reads that holds nothing — including the case where a field is absent-read and was never recorded anywhere. That is the difference between no test reaches these and no test was looked for, and it is the intended instrument for §2.4 going wrong.


5. The fact-writing contract

The vocabulary of §2 and §3 is what a producer and a module must agree on. There is no negotiation and no error at load time for disagreeing: a fact whose field the module does not declare is written, stored, and read by nothing.

5.1 Requirements on a producer

  1. A producer MUST write only into fields the module declares (§3.3). A field the module does not declare is a fact that lands where nothing reads.
  2. A producer MUST NOT write a derived field (§3.4) or a rule conclusion (§3.5).
  3. A producer MUST write absence-read fields only where true (§2.4).
  4. A producer MUST use subjects, not display names, wherever a field holds a reference to another fact (§3.3, Call).
  5. A producer MUST NOT assert an edge it did not resolve; it SHOULD record the unresolved call instead (§3.3, UnresolvedCall).
  6. A producer SHOULD write every field it can determine. Writing fewer narrows what the module can conclude; it does not make anything wrong.

5.2 What is normal and not an error

A field the module declares that no producer writes is fine, and expected. Several exist. Some a customer supplies by hand — Function.deprecated. One whole kind, Counterpart, is written by nothing LangSyn publishes, because it records a claim about intent that only a reader can make.

This asymmetry is deliberate: the contract is one-way. A module may declare more than any producer writes. A producer may not write more than the module declares.

5.3 Compatibility

  • Adding a class, a field, a rule or a query to a module is a compatible change. Existing producers keep working; the new field is simply unwritten.
  • Removing or renaming a class or field a producer writes is a breaking change, and the module’s version reflects it.
  • Changing a rule’s threshold changes what your store concludes without changing any name. It is a semantic change, and it is checked: the thresholds in §3.5 and the module’s own must agree, so a threshold cannot move without this document moving with it.
  • Changing a rule’s condition in any other way — which fields it reads, how they combine — is a semantic change that no mechanical check sees (§5.4). It is visible in the module’s version and in §3.5’s prose, and nowhere else.
  • The two artifacts version independently: dkepy and each module carry their own versions, and this specification carries its own. Read a version from the artifact it belongs to, never from prose.

5.4 What is mechanically checked, and what is not

Stated plainly, because the gap is where a producer author will otherwise assume protection that does not exist.

Checked.

  • One direction of §5.1(1): a field written by any dkepy emitter that the module does not declare fails a gate before release. The check spans every generation of the emitter the published surface has ever held, not just the current one, because a customer may hold any of them.
  • This document against the module it specifies, in both directions: every kind, field, derived field, rule and query the module declares is named here, and every rule, query and derived field this document promises exists there. Rule thresholds, the kind a rule ranges over, the field it concludes, and a derived field’s definition are compared as values, not merely as names.

Not checked, and each of these is real:

  • Types. A field declared string that a producer writes as an int passes every check here.
  • What a rule reads. Thresholds are compared, but the rest of a condition is not: a module can keep every field name and every threshold and still change which fields a rule combines, or how.
  • Producers we do not publish. A producer you wrote is bound by §5.1 and measured by nothing. checkup is the instrument that answers from your own store.
  • Absence discipline. §2.4 is a requirement no gate enforces. Its failure mode is a plausible answer rather than an error, which is exactly why it is written as a MUST.

6. What this specification does not cover