Index
Certus reads your repository into a layered model of what it contains and how it connects. Everything downstream — the diff, the claims, the frontier — is derived from it.
The eight layers
A repository is modelled in layers, each answering a different question and each independently available. A frontier that needs a layer you do not have is not realizable, which is why availability is reported per layer rather than as one “indexed” flag.
- L0 structure
- Symbols, definitions, imports and approximate calls, in every language.
- L1 resolved
- Precise definition and reference edges, and type information, where a language indexer exists.
- L2 interfaces
- HTTP routes, GraphQL schemas, gRPC services, command-line commands, assistant tool registrations, event handlers, cron entries.
- L3 data
- Tables, columns, relations and migration ordering, from migrations and models.
- L4 runtime
- What the application actually exposes when booted — real routes, processes, ports, variables read — reconciled against what the code claimed.
- L5 product
- Screens, user actions and known paths, from routers and component trees.
- L6 tests
- Which tests exercise which nodes.
- L7 semantics
- Summaries used for retrieval and for deriving observed claims.
Each layer reports whether it is complete, partial or absent, with a coverage figure and a stated reason. A run cannot proceed without structure: no parseable source means the run is blocked, not proved.
Stable identity and renames
Every node gets an identity built from its kind, language, qualified path and a hash of its signature — never a file path. That is what lets Certus say a function moved rather than that one vanished and another appeared.
Renames are detected before anything is diffed, by comparing normalized bodies with docstrings stripped and local names folded, and by how much the set of things a node references overlaps. Getting this wrong is expensive in a specific way: an undetected rename looks like a deletion plus an addition, which manufactures claims nobody made.
Nodes come from a closed vocabulary — functions, classes, routes, GraphQL fields, gRPC methods, commands, tools, topics, exports, tables, migrations, tests, docs, components, manifests, config, infrastructure, flags — and so do the edges between them. An extractor that emits something outside the vocabulary fails validation rather than being quietly reclassified.
Why it is fast, and never stale
Index entries are keyed by content, not by time. A file's analysis is keyed by the hash of the file's bytes and the version of the analyzer that read it; a commit's model is keyed by the repository and the commit. A completed commit model is immutable.
So there is no cache to invalidate, no time to live, and no manual flush — because there is no mutable derived state. A file's analysis is never stale: its key is its content.
The one thing that forces recomputation is a change to an analyzer or extractor, because the version is part of the key. That is deliberate, and it is the mechanism by which a fix to an extractor reaches every repository.
A typical pull request touches a handful of files, so an incremental index re-analyzes those blobs and re-resolves the packages that contain them plus their immediate importers. The first index of a large repository is different: it runs as its own job when you connect the repository, never inside a pull request. A run arriving during it is blocked with a reason, never served a partial model presented as complete.
Static claims against a booted application
Interface extractors read your code without executing it, because the repository is hostile input. Booting the application produces a second, independent list of what it actually exposes. Joining the two is reconciliation, and the result is a status on every interface — never an exception.
- confirmed
- Both sides agree on the interface and its shape. The frontier through it is fully realizable.
- static_only
- The code declares it; the running application does not expose it. Not realizable in this environment, so claims fall back or defer as debt.
- runtime_only
- The application exposes it; no source attribution exists. Usable as a frontier, but the grade caps at B.
- shape_mismatch
- Both sides have it and disagree about its shape. Realizable, but the contract is unverified, so schema obligations cap at C.
A mismatch never fails a run. It becomes a finding naming the three things it could be — the extractor is wrong for this framework version, the application is misconfigured for this environment, or the route is registered dynamically — and Certus states plainly that it cannot tell which. Dynamic registration is only ever discoverable at run time, and that is an accepted permanent limit of reading code without running it.
When it says something unexpected
- “That commit has not been indexed”
- The model for that commit does not exist yet. If you just connected the repository, the first index is still running.
- A route your code clearly declares is missing
- Check its reconciliation status. Static-only means the booted application did not expose it — usually a configuration difference between your environment and the recipe.
- A layer reports as absent
- That layer had no producer for this repository. Frontiers that need it are not realizable, which is why a claim may be proved somewhere smaller than you expected.
- A node that looks wrong
- The fix is in the extractor, not in your repository. Every row records which analyzer version produced it, so a bad result is attributable and a corrected extractor recomputes it everywhere.
- A rename reported as a delete plus an add
- The body changed too much to match. Worth knowing, because it changes which claims are derived.