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.
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.