candor · the specification

One contract.
Every language reads the same.

“What does this code do?” takes language-specific machinery to answer — type resolution, call-graph construction — so candor’s engines are bespoke per toolchain. The output is not: one effect vocabulary, one report schema, one trust contract. An agent, a human or a CI gate reads a Rust report and a JVM report identically.

Read SPEC.md candor-spec v0.4

01 The effect vocabulary

An effect is an observable interaction with the world outside pure computation. Ten of them, shared by every implementation:

Net · Fs · Db · Exec · Env · Clock · Ipc · Log · Rand · Clipboard

And an eleventh, Unknown, which is mandatory: a call the engine could not resolve — dynamic dispatch with no visible target, a callback, reflection — contributes Unknown rather than being silently assumed pure.

Plain console writes are deliberately unclassified — printing is a CLI’s purpose, and classifying it would drown every report in noise. Log means a call into a logging framework, which is an architectural fact.

02 The report

Per compilation unit, a self-describing envelope — which engine produced it, against which spec — and one entry per function:

{
  "candor":    { "version": "<engine build id>", "toolchain": "<channel>", "spec": "v0.4" },
  "functions": [ {
    "fn":         "pricing::quote",
    "loc":        "src/pricing.rs:41:1",
    "inferred":   ["Net", "Clock"],   // transitive: this fn + everything it calls
    "direct":     ["Clock"],          // performed in its own body
    "unresolved": false,              // true ⇒ the set may be incomplete
    "hosts":      ["rates.internal"]  // literal endpoints only — never guessed
  } ]
}

inferred must be transitive across every call — including calls into another package of the same project. The refinement fields (hosts, cmds, paths, fs) carry only what is statically visible as a literal: a present value is always sound, an absent one is never a claim of absence. Alongside the report, a call-graph sidecar records every function’s edges — including pure ones — so “who transitively calls this?” is answerable before an edit, not after.

03 The trust contract

An implementation must never report a function as effect-free when it could not actually determine that.

That single rule is the core of the spec. Resolved effects are authoritative; anything unresolved is Unknown, flagged per function, with an optional unknownWhy that tells irreducible opacity (reflection, native code) from the improvable kind (a missing implementation — widen the analysed inputs). A consumer knows exactly when to trust the report and when to go read the source.

04 The policy grammar

Gates read one policy file, and its grammar is normative — a rule means exactly the same thing in every language. Four rule kinds:

deny    Net Db domain        # the domain layer performs no Net/Db
pure    parse                # parse must be effect-free
allow   Net in billing api.stripe.com   # an enforced endpoint allowlist
forbid  domain -> infra      # dependency direction, checked over the call graph

Scopes match by path segment, never substring (domain matches app::domain::handle, not subdomain); a malformed line is dropped with a warning, never silently reinterpreted — the one thing a security gate must not do.

05 Conformance is executable

The spec ships a differential suite: the same fixtures implemented in each language, with CI failing if any two engines disagree on an effect set or a policy verdict. A deny Net boundary is enforced identically across a polyglot codebase by construction — the line per-language rule sets can’t cross, because rules per language carry no guarantee they mean the same thing.

The spec is also written to be implemented without its authors: a TypeScript engine derived from the documents alone scores 20/20 on the suite and runs in its CI as the third engine. And every conformant implementation must analyse itself cleanly and should gate its own build with its own policy — an effect gate whose own gate is red is asking adopters to hold a standard it doesn’t hold itself.

Engines today: Rust (shipped — the reference implementation) and the JVM (prototype — full mode set, Spring-aware), with C# and Go planned. The full contract is eight MUSTs — typed call resolution, transitive effect sets, the report schema, the trust contract among them — in SPEC.md §7.