# 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](/spec/dke/library/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](/ref/dke/reasoning-modules/) 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](/ref/dke/mcp/). **Out of scope.** The DKE Python language itself ([Language Specification](/spec/dke/python/)); the wire protocol ([DKE MCP Reference](/ref/dke/mcp/)); 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](/spec/dke/python/). 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](/ref/dke/python/#modules). 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`. > `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. **`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. `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 .` 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 - **The DKE Python language** — [Language Specification](/spec/dke/python/). - **The wire** — [DKE MCP Reference](/ref/dke/mcp/), including the `language` parameter and its accepted values. - **How to use a module** — [Reasoning Modules Tutorial](/tutorial/dke/reasoning-modules/). - **Your store's contents**, which are yours, and which this document deliberately says nothing about.