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 · Llm · Exec · Env · Clock · Ipc · Log · Rand · Clipboard
And a twelfth, 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": "0.24" },
"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 unknownWhy (required on every
direct Unknown source since 0.6) 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.
Since 0.8, engines discover the policy from a checked-in .candor/config and the gate
emits a machine-readable verdict via --gate-json, so CI surfaces (a PR annotation, a
SARIF upload) consume the same result the exit code reports.
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: the TypeScript engine was derived from the documents alone, and a multi-part cross-engine differential runs in CI to hold every engine to the same answers. 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: the JVM (the reference implementation — Java, Kotlin, Scala, Groovy), with Rust, TypeScript/JavaScript and Swift completing the conformance-pinned four, and candor-agents applying the same contract to agent fleets; C# and Go are 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.
Work with the team behind candor
candor is built and run by Polymorphism — a small senior team that designs, builds and runs large systems for enterprise and government. The judgement we put into AI-code safety here is what we’d bring to your platform.