RFC reference: RFC-cfdb.md §A1.4.
Pattern C identifies wiring bugs in which a canonical resolver for a
concept exists in the graph (declared via
(:Item)-[:CANONICAL_FOR]->(:Concept) edges emitted by
enrich_concepts) but some call sites resolve the concept by calling a
non-canonical wire-level form instead. The generalized v0.2 rule
supersedes the v0.1 ledger-canonical-bypass.cypher specialized
instance (commit 349b153d6) with parameterization on concept name,
bypass method name, and caller scope.
The v0.2 rule does not fit in a single .cypher file because the
cfdb-query parser does not support UNION or CASE WHEN. The four
verdicts split into four files:
| Rule file | Verdict | Action hint |
|---|---|---|
canonical-bypass-caller.cypher |
CANONICAL_CALLER |
OK — healthy wiring, no action |
canonical-bypass-reachable.cypher |
BYPASS_REACHABLE |
Rewire — replace bypass with canonical call |
canonical-bypass-dead.cypher |
BYPASS_DEAD |
Delete — bypass exists but no entry point reaches it |
canonical-unreachable.cypher |
CANONICAL_UNREACHABLE |
Wire bypass callers through, or delete the canonical |
All four rules live at examples/queries/.
Every Pattern C rule requires three enrichment steps before it reads correctly:
cfdb extract --workspace <dir> --db <db> --keyspace <ks> --hir
cfdb enrich-concepts --db <db> --keyspace <ks> --workspace <dir>
cfdb enrich-reachability --db <db> --keyspace <ks>
--hirpopulates:EntryPointnodes +CALLS/INVOKES_ATedges. Without it,enrich_reachabilitycannot seed BFS and returnsran: false, which causes every reachability-gated rule to return an empty result (silent no-op — see the per-file docs).enrich-conceptsreads.cfdb/concepts/<name>.tomland emits:Concept+LABELED_AS+CANONICAL_FOR. Without a TOML declaration, there is no canonical anchor and Pattern C has nothing to check.enrich-reachabilityBFS's from every:EntryPointoverCALLS*, writing:Item.reachable_from_entry(bool) +:Item.reachable_entry_count(i64). The Pattern C rules filter on these attrs.
The rest of this page walks one row per verdict against the synthetic
fixture at
examples/queries/fixtures/canonical-bypass/.
The fixture layout:
ledger/— a canonical crate declaringLedgerRepository::append(non-canonical) andLedgerRepository::append_idempotent(canonical), plus fourLedgerServicemethods exercising every verdictcli/— a#[derive(Parser)]struct (the:EntryPointanchor) that fans out to two of the service methods.cfdb/concepts/ledger.toml— declaresledgeras the concept andledgeras the canonical crate
cfdb query --params '{
"concept": "ledger",
"canonical_callee_name": "append_idempotent",
"caller_regex": ".*::LedgerService::.*"
}' "$(cat examples/queries/canonical-bypass-caller.cypher)"
Expected row (excerpted):
{
"concept": "ledger",
"call_site": "append_idempotent",
"caller": "ledger::LedgerService::record_trade_safe",
"verdict": "CANONICAL_CALLER",
"evidence": "ledger/src/lib.rs"
}When to act: never. These are the call sites that already resolve
the concept through the canonical wire. Keep them. This rule exists to
give the triage story its baseline — if a bypass rule fires, the
CANONICAL_CALLER rule on the same fixture confirms the canonical form
IS in use elsewhere (so the bypass is a migration gap, not a total
miss).
cfdb query --params '{
"concept": "ledger",
"bypass_callee_name": "append",
"caller_regex": ".*::LedgerService::.*"
}' "$(cat examples/queries/canonical-bypass-reachable.cypher)"
Expected row:
{
"concept": "ledger",
"call_site": "append",
"caller": "ledger::LedgerService::record_trade",
"verdict": "BYPASS_REACHABLE",
"evidence": "ledger/src/lib.rs"
}When to act: always. The caller is reached from the CLI
:EntryPoint, so a user action can trigger this code path. Replace
.append(...) with .append_idempotent(external_ref, ...). This
row is the #3525 class of bug — a user-triggerable write path that
bypasses an idempotency barrier.
cfdb query --params '{
"concept": "ledger",
"bypass_callee_name": "append",
"caller_regex": ".*::LedgerService::.*"
}' "$(cat examples/queries/canonical-bypass-dead.cypher)"
Expected row:
{
"concept": "ledger",
"call_site": "append",
"caller": "ledger::LedgerService::record_orphan",
"verdict": "BYPASS_DEAD",
"evidence": "ledger/src/lib.rs"
}When to act: delete. The bypass call site exists but no entry point
reaches it. This is the #3544/#3545/#3546 class of bug: a resolver path
scatters, leaving one branch stranded behind an unwired helper. Dead
code is cheaper to delete than to rewire — unless the unreachability is
itself the bug (in which case it will also surface under
CANONICAL_UNREACHABLE).
cfdb query --params '{"concept":"ledger"}' \
"$(cat examples/queries/canonical-unreachable.cypher)"
Expected row (one of several — every :Item in the canonical crate
that is unreached surfaces):
{
"concept": "ledger",
"canonical_item": "ledger::LedgerService::record_isolated",
"call_site": "(no call site — canonical impl is unreachable)",
"caller": "ledger::LedgerService::record_isolated",
"verdict": "CANONICAL_UNREACHABLE",
"evidence": "ledger/src/lib.rs"
}When to act: wire or delete. The canonical impl exists but no
:EntryPoint reaches it, so either callers need to migrate onto it
(the common case — they're using the bypass) or the canonical impl is
orphaned and should be removed. This is the #1526 class of bug: a
safety envelope (LiveTradingService) declared canonical but wired
around instead of through.
For a given concept, the expected reading order is:
- Run
canonical-bypass-reachable.cypher— any row here is a LIVE bug. Fix immediately (rewire). - Run
canonical-unreachable.cypher— any row here is a SAFETY gap. Determine whether the canonical impl should be wired in or deleted. - Run
canonical-bypass-dead.cypher— any row here is dead code. Delete. - Run
canonical-bypass-caller.cypher— confirms the healthy wiring baseline exists. If this is EMPTY while the others have rows, the canonical form is unused everywhere — a larger architectural issue than the Pattern C rule alone can characterize.
- No UNION / CASE in the cfdb-query parser → four files instead of
one. When the parser gains these constructs, the four rules can
collapse into a single
canonical-bypass.cypherwith aCASE-derivedverdictcolumn. $canonical_callee_name/$bypass_callee_nameare manual — the concepts TOML declares which CRATE is canonical, but not which METHOD NAME on that crate is the canonical form. A concepts TOML extension addingcanonical_method_patterns = ["append_idempotent"]would let the rule derive both params automatically; deferred to a follow-up.CANONICAL_FORis crate-wide in the currentenrich_conceptsemission rules.canonical-unreachable.cyphertherefore can surface helper items (build_entries, trait decls) that share a crate with the canonical method but aren't themselves the canonical resolver. The rule treats this as acceptable noise — the row'scanonical_itemcolumn makes the shape obvious, and narrowing viacanonical_item_patternsis the same concepts-TOML extension noted above.cfdb violationshas no--paramsflag in v0.2. Usecfdb querywith the rule content piped in via"$(cat <rule>)"untilviolations --paramsships.
| Issue | Shape | Verdict in fixture |
|---|---|---|
| #3525 | LedgerService::record_trade calls .append() not .append_idempotent() |
BYPASS_REACHABLE (via record_trade) |
| #3544 / #3545 / #3546 | parse_params / build_resolved_config scatter — bypass stranded behind unwired helper |
BYPASS_DEAD (via record_orphan) |
| #1526 | Capital.com LiveTradingService safety envelope declared canonical but wired around |
CANONICAL_UNREACHABLE (via record_isolated) |