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.
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. |
Class — name: string, module: string, methods: int, language: string (optional, as on Function).
Call — caller: string, callee: string, site: string.
callerandcalleeMUST hold the subject of aFunction, 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.
UnresolvedCall — caller: 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.
Rationale — subject: 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.
Import — importer: string, imported: string, root: string.
Dependency — name: string, version: string, direct: bool, pinned: bool.
Counterpart — name: 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.
relationnames what is being counted, because it differs per pair;left_coversandright_coverscount 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,> 5select 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.
untestedanduncalled_fncarry 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.Classis spelledKlassin Python, becauseclassis a Python keyword. The fact it writes iscode.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
- 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.
- A producer MUST NOT write a derived field (§3.4) or a rule conclusion (§3.5).
- A producer MUST write absence-read fields only where true (§2.4).
- A producer MUST use subjects, not display names, wherever a field holds a reference to another fact (§3.3,
Call). - A producer MUST NOT assert an edge it did not resolve; it SHOULD record the unresolved call instead (§3.3,
UnresolvedCall). - 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:
dkepyand 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
dkepyemitter 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
stringthat a producer writes as anintpasses 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.
checkupis 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
- The DKE Python language — Language Specification.
- The wire — DKE MCP Reference, including the
languageparameter and its accepted values. - How to use a module — Reasoning Modules Tutorial.
- Your store’s contents, which are yours, and which this document deliberately says nothing about.