candor-swift v0.27.0
[0.27.0] — 2026-08-07
-
An EMPTY SCAN now reaches the machine channel.
no Swift sources under <target>exited raw, so a
consumer reading--gate-json -got nothing — an ordinary CI accident (a path that moved) and the
last cause in this engine still exiting that way. Pinned by conformance PART 36 (b17). -
An UNREADABLE config left a stale green at the FILE sink, while the same cause streamed its
refusal correctly. The §3.3.1 pre-pass reads the config to learn what the sink must not overwrite and
runs BEFORE arming; its own comment said that read was "LENIENT — no exit, no diagnostic", which was
an assumption about the reader rather than a property of it. Three exit sites in that reader could
fire, so the process died before arming and a previous run'sok: truesurvived on disk — the exact
outcome arming exists to prevent. Third engine with this shape (ts and agents had it), and the first
where only ONE of the two sink forms was affected: the stream was already right, which is why it took
a file-sink probe to see. Pinned by conformance PART 36 (b15). -
…and the guard now enumerates a dep directory exactly as the LOADER does. The first repair
registered the directory's files with a FLAT read beside a RECURSIVE loader walk, so a report one
level down stayed unguarded — and for--deps, which writes one subdirectory pername@version, the
nested layout is the ORDINARY one. A guard that enumerates differently from the loader guards a
different set of files. One enumeration now serves both. -
⚠ A
--gate-jsonsink INSIDE adepsDIRECTORY destroyed the operator's dep report.deps
accepts a directory —--workspacewrites.candor/deps/and hands that back, so it is the common
spelling — and the loader walks it and reads each report inside. The §3.3.1 sink-over-input guard
registered only the DIRECTORY, which never equals a file within it, so--gate-json <depdir>/lib.json
was unguarded: arming destroyed the report, the run chained the wreckage and exited 0 withok: true
written over the input. All four engines. The FILE spelling of this channel had been guarded for a
release; the directory spelling had not, and no row posed it. Now pinned by conformance PART 36 (b14),
which asserts both the refusal AND that nothing was written. -
A gate-adjacent flag given NO VALUE now reaches the machine channel.
--policy,--outand
--gate-jsonwith a missing value exited raw, so--gate-json -got nothing — the last cause in this
engine still doing that after every other had been routed, and the one §3.1 names beside the unknown
flag. rust and ts already answered it. Found by sweeping the causes a user can TRIGGER rather than by
reading exit sites, which is a different list and a shorter one. -
The configured-dep refusal reaches the machine channel.
depsFailexited raw, so--gate-json -
got nothing and a file sink kept the armed placeholder rather than the reason. PART 35 has pinned the
EXIT CODE for this cause in four engines for a release; nothing pinned the CHANNEL until PART 36 (b8). -
The
gateverb registers its stream sink before anything can exit, and writes it once.
refuseGateAndExitalready knew how to write-to stdout; the gate verbs pre-pass never put it in
the sink list, so an exit-2 during argument parsing left stdout empty. Registering it then exposed the
mirror — the flag loop registered the same sink again, and one exit-2 wrote the refusal document
TWICE. Deduped at the write, which covers every appender rather than the two that exist today. -
⚠ MODULE IDENTITY IS NOW PER FILE, AND HONOURS THE DEPENDENCY GRAPH. The rung the entry below
promised, built and reviewed three times. It replaces the withdrawal: the improvement that entry gave
up is here, on evidence rather than on filesystem shape.The question the disclosure channels ask is per FILE: can THIS file's package — or THIS file's
Xcode target — import THAT module? It was being answered per SCAN: a name claimed anywhere silenced
it everywhere. That mismatch is what nine earlier review rounds kept rediscovering, and no bound on
the derivation fixes it, because the derivation was answering the wrong question.A file's owning package can import its own targets plus, through each
.package(path:)it declares,
the PRODUCTS those local packages expose — the product is the unit of exposure, the target is the unit
of import, and they are not interchangeable. An.xcodeprojtarget has noPackage.swift, so its
answer comes from the closure the--targetresolver already walked: the products it links, plus the
graph behind those specific products, since Xcode puts the whole reachable graph on a target's import
path. Anything unreadable at any step — a computedpath:, a non-literaltargets:, no owning
package at all — yields no claim, so the module stays named.Measured, 20
--targetscans across NetNewsWire, IceCubesApp and firefox-ios. THIS entry's change
leaves NetNewsWire and firefox untouched. Their gains come from two OTHER entries below — an Xcode
target being a module, and the path-normalization fix — so on the released build firefox'sClient
reads 17608 analyzed functions and 51 uncovered modules, against 17562 and 55 at the start of this
release's work. NOT "against 0.26": the released 0.26 engine has no--targetflag at all and cannot
produce those numbers. Every before/after figure in this section is measured against430c5ef, the
commit this thread started from, which is already past 0.26. What THIS one moves is IceCubesApp's app target: 48 uncovered modules to 38, and all
ten names removed have analyzed functions in the same report (StatusKit 284, Account 109,
DesignSystem 46, …). The 279 entries that leavefunctionswith
them carried no inferred effect between them — each held only aninvisiblehedge for a module this
run had read.The invariant that makes this safe to state: every name that can be claimed internal has passed
analyzedTargets, so a module is called internal only if a file under that target's real source root
was read in this run. Absence is still never a claim of purity for anything unread.