Rule criteria
A requirement's rule: criterion carries a
CodeGuard rule DSL expression that must hold against your code. Unlike
test:, manual:, integration:, and external: — which record a pin
against something evaluated elsewhere — a rule: criterion is evaluated by
CodeCharter itself, against the same graph codecharter analyze builds.
codecharter req check compiles the expression, runs it against the analyzed
model, and reports one of three verdicts.
The three verdicts
Holds. The rule compiled, ran, and found nothing to complain about. This is not proof the requirement is airtight — it is exactly what the rule you wrote actually checks, no more, no less.
Violated. The rule found at least one place in the code that fails it. The
report names the concrete offending entity and, where the criterion is
type-shaped (a Reaches/ReachedBy predicate over types), a witness — one
real path from a matching source type to a matching target type, rendered as
Type -[kind]-> Type. The verdict is decided solely by the rule's own
evaluation; the witness is decoration on top, so a criterion still reads
Violated even on the rare case a witness could not be rendered (the report
then says path-unavailable: <reason> instead of a chain).
Unknown. Something about the criterion or the code could not be resolved one way or the other. This is the important case — see below.
What Unknown means in 1.6.0
Unknown is CodeCharter telling you it cannot answer, not that it noticed everything and found nothing wrong. Concretely:
- A rule that queries a construct CodeCharter's static analysis genuinely cannot see through — an unrecognized reflection call, a delegate invocation, a dispatch path that only resolves at runtime — comes back Unknown rather than silently reading as Holds. Before 1.6.0 (and for anything this release still does not recognize), the same construct reads as Holds: static analysis that cannot see a call simply does not see it, which is indistinguishable from "there is nothing there" unless the tool says so explicitly. Rule criteria say so explicitly for the constructs listed below; everything else keeps the older, honestly-limited behavior.
- Dataflow analysis, taint tracking, and timing/concurrency properties are not
attempted at all. A
rule:criterion that needs one of those stays a job for a real test (test:), deliberately absent here rather than faked with a check that looks thorough but is not. - A typo'd type-name glob (
Reaches(t => t.Name.Matches("Sevrice"))) that matches zero types in your solution is Unknown, not a vacuously-true Holds — a criterion that silently checks nothing is worse than one that says so.
Unknown tokens
Every Unknown verdict names one specific edge id, so the report is
actionable rather than a bare "something is wrong". Each token is either
Tier A — a defect in the criterion or the run itself, never
acknowledgeable, since there is nothing about your code to accept — or
Tier B — a genuine gap in what this analysis can see, which you can
knowingly accept via acknowledge: (see below).
| Edge id | Tier | Meaning |
|---|---|---|
analysis-unavailable |
A | No code model was analyzed for this run. |
criterion-not-compilable |
A | The criterion's DSL could not be parsed. |
criterion-schema-mismatch |
A | The criterion queries a root collection this engine version does not define. |
criterion-param-unbound |
A | The criterion declares an @param with no default that received no override. |
criterion-evaluation-error |
A | Evaluating the criterion raised an error (e.g. an unknown member on one entity). |
pattern-matches-no-type |
B | A Reaches/ReachedBy type-name glob literal matches zero types in the solution. |
dispatch-divergence |
B | The criterion holds under declared calls but is violated under virtual dispatch, or vice versa. |
opaque-site |
B | An invocation inside a criterion-selected type targets a recognized reflection/opaque-dispatch construct (Activator.CreateInstance, Type.GetType, MethodInfo.Invoke, a delegate-typed parameter/field invocation, …) that static analysis cannot resolve to a concrete callee. |
A rule: criterion whose analyzed model lost call-edge information (an
ambiguous overload, a dynamic call) still gets a Holds or Violated verdict —
that gap does not block the criterion — but the report adds a caveat sentence
naming how many invocations were affected, so the gap is visible rather than
silent.
Acknowledging an Unknown
A Tier B unknown blocks the requirement until you either fix the code (make
the construct analyzable, correct the glob typo) or knowingly accept it with
an acknowledge: entry on the criterion:
criteria:
- id: C1-1
rule: Types.Where(t => t.Name.EndsWith("Repository")).All(t => t.ImplementedInterfaces.Any(i => i.Name.EndsWith("Repository")))
acknowledge:
- edge: "opaque-site:M:MyApp.Data.RepositoryFactory.Create#Activator.CreateInstance"
reason: "Factory is a closed set of three known repository types, reviewed 2026-09-02."
Both edge and reason are required — an entry without a reason is
reported as req-acknowledge-missing-reason and evaluates as if it had not
been written. edge must match an unknown edge's id exactly; ordinal-sorted
by id, so the same fixture produces the same order every run.
A Tier A unknown can never be acknowledged: naming its id in acknowledge:
has no effect, because there is no property of the code to accept, only a
defect in the criterion or the run — fix the criterion instead.
Edge ids and renaming
An edge id carries no line numbers, so reformatting the file does not
invalidate an acknowledgment. It does carry the declaration or member the
unknown is anchored to (e.g. M:MyApp.Data.RepositoryFactory.Create in the
example above). Renaming that declaration changes its id, which changes the
edge id — by design: the acknowledgment was reviewed against a specific
named thing, and a rename means a human should look at it again, not that
the old sign-off silently keeps applying to whatever the declaration is
called now.
An acknowledge: entry that no longer matches any current unknown edge (the
gap was fixed, or the thing it named was renamed) is reported as a stale
acknowledgment — visible for cleanup, but it never flips a verdict on its
own.
See codecharter req check for exit codes and output
formats.