11 KiB
Graph model
Brick separates analysis from presentation:
analyzers canonical model consumers
grammar / lexical -\
YAMS graph ----------> nodes + typed edges ----------> JSON
Fold source/binary --/ evidence + metrics --------> Cytoscape
future traces ---------------------------------------> Fold/MCP
Compatibility
Schema version 4 makes evidence multiplicity, the selected P4 frontend in scan
scope, and the nested P4 metric contract reader-visible. Readers must reject unsupported schema versions
instead of guessing. Two models
are structurally comparable only when they have the same schema version,
canonical scan root, repository identity, and scan scope. The top-level root
is absolute execution provenance; comparing artifacts from different clones or
worktrees can opt into --allow-root-relocation for structural diffs. This caller
assertion relaxes only root equality and is recorded in diff schema 2 alongside
both original roots. It does not verify Git identity; the repository node ID is
graph-local, not a repository-origin fingerprint. See
structural diff.
scope.path is normalized and repository-relative. Each
scope.excluded_directories value is one non-empty path component; the array is
sorted and deduplicated before serialization. Readers reject absolute,
drive-qualified, traversing, unsorted, duplicate, or empty scope values.
scope.p4_frontend is lexical or the
explicit tree-sitter-p4-pilot; models from those frontends are not comparable
because their graph coverage differs. Future scope fields must become part of
the compatibility check when they can change graph coverage. The top-level
root must be a normalized portable absolute path (/repo, C:/repo, or a
normalized UNC root); empty, relative, drive-relative, backslash, trailing
separator, traversal, and unnormalized Windows verbatim forms are rejected.
The scanner converts \\?\C:\repo to C:/repo and
\\?\UNC\server\share to //server/share before serialization.
Compatible buildings can be compared through the separate versioned structural
diff contract. It preserves complete added/removed facts and before/after
records for shared IDs whose content or evidence changed. See
docs/architecture/structural-diff.md. Versioned declarative boundary policy
is a separate consumer of canonical dependency edges and never changes their
kind or evidence; see docs/architecture/policy.md. The read-only MCP server is
another consumer: it validates schema-v4 JSON once, then exposes versioned
impact, cycle, bridge, and evidence queries without scanning or renderer
coupling; see docs/architecture/mcp.md.
Nodes and identity
Exactly one Repository node is required; missing or ambiguous repository identity fails schema validation. Duplicate node/edge IDs, cross-kind ID collisions, dangling edge endpoints, and edges without evidence also fail schema validation. Repository and directory nodes provide compound layout. File, class, and function nodes represent source structure. External nodes make unresolved dependencies visible instead of dropping them. Finding nodes attach analyzer evidence without changing structural identities.
IDs derive from kind plus repository-relative semantic identity. The repository
node uses . as its identity rather than the checkout directory name. Source
line is provenance, not identity. Extracted symbols use:
path + semantic owner ID + coarse kind + name + same-name ordinal
The ordinal distinguishes overload-like declarations within one owner. This keeps IDs stable under unrelated line insertions, though inserting a same-name declaration before another remains an explicit lexical limitation.
The default C++/JS/TS/Dart/Rust grammar extractor preserves this identity format
for unchanged declarations. Namespace/module scopes additionally contribute to
identity and appear in attributes["source.scope"]; ordinals are local to that
scope. Rust impl methods use their resolvable type as parent. Fixing a previously
incorrect owner intentionally changes the affected method ID. Unresolved targets
in these languages include source, language, and scope in identity so unrelated
unknowns cannot merge. Legacy Python/Swift/P4 identities remain unchanged.
source.exports records syntax-level export names; it does not assert that a
module is reachable under a particular build configuration. Parameter-bound calls
retain source.unresolved-reason instead of resolving to a same-named function.
Grammar version and extraction revision live in evidence backend strings.
Schema 4 remains sufficient: consumers use the same node/edge kinds and evidence
contract, with optional opaque attributes. Rebuild baselines when adopting the
new extractor so corrected coverage is not mistaken for a source-code change.
P4 support initially retains the coarse display kind only when the exact domain kind is also present in node attributes:
domain = "p4"
p4.kind = "header" | "header-union" | "struct" | "parser" |
"control" | "table" | "action" | "state" | "extern" |
"terminal-state" | "unresolved"
p4.dialect = "p4-16"
An architecture attribute is added only when an explicit source or imported
contract supplies it; the lexical scanner does not infer v1model from familiar
shapes. Parser states and other nested entities use their semantic owner as parent.
A consumer must not infer P4 semantics from class or function alone.
Edges and evidence
Contains and defines describe hierarchy. Calls, imports, and inherits describe dependencies and participate in the current SCC/coupling metrics. Flags attaches analysis evidence and does not affect the structural score.
An edge has a stable relationship identity, optional attributes, and an ordered
evidence array. The relationship is deduplicated by (source, target, kind);
independent call sites and backends add evidence records rather than replacing
one another. Evidence ordering is deterministic by lane, backend, provenance,
and confidence.
P4 relationships retain their exact domain meaning in edge attributes even when a coarse edge kind is used:
domain = "p4"
p4.relation = "direct-transition" | "select-transition" |
"select-default" | "apply" | "action-call" |
"table-action" | "include" | "compiler-diagnostic"
Every evidence record has:
- lane: syntactic, symbolic, imported, or heuristic;
- backend: producer and mode;
- confidence: a producer-local numeric score, categorical label, or
nullwhen the producer did not supply one; - provenance: source location or report artifact.
Confidence labels from different producers are not calibrated probabilities. Missing or unknown imported values remain unknown or produce diagnostics; they must not be mapped to a convenient backend, lane, or scalar.
Lane classification
| Fact | Lane |
|---|---|
| Filesystem containment or declaration syntax | Syntactic |
| Lexical/tree-sitter target resolution without compiler proof | Heuristic |
| External analyzer diagnostic | Imported |
| Result proved within a named bounded model | Symbolic |
A syntactic call expression, heuristic target resolution, imported diagnostic, and bounded symbolic fact may all support the same relationship. Name resolution never upgrades existing evidence confidence or lane.
Health indicator
The v0.1 score remains deliberately transparent:
| Component | Weight | Meaning |
|---|---|---|
| Acyclicity | 35% | Fraction of symbols outside cyclic SCCs |
| Resolution | 25% | Dependency edges not ending at external nodes |
| Connectedness | 25% | Symbols participating in dependencies |
| Concentration | 15% | Penalty for one target dominating fan-in |
The score is experimental. It is a navigation aid, not an architectural quality oracle. Future weights require labeled multi-project evidence and an ablation; they must not be tuned against one repository.
The top-level score remains the original coarse, language-agnostic indicator;
its SCC inputs still include calls/imports/inherits, including coarse P4 edges.
Consumers that need P4 meaning must use metrics.p4, whose fields are:
| Field | Canonical observation |
|---|---|
parser_state_count / terminal_state_count |
P4 state and implicit terminal-state nodes |
parser_transition_count |
Transition edges sourced at a parser state |
parser_cycle_count / cyclic_parser_state_count |
SCCs over same-parser direct-transition, select-transition, and select-default edges |
unreachable_state_count |
Non-terminal states not reachable from a recognized start state within the same parser |
control_apply_node_count |
P4 control, table, and action nodes |
control_apply_relationship_count |
apply and action-call edges sourced at those nodes |
control_apply_cycle_count / cyclic_control_apply_node_count |
SCCs over resolved control/table/action apply and action-call edges |
unresolved_dependency_count |
P4 include, transition, apply, action-call, or table-action edges targeting an External node |
A repeated apply or action call merges call-site evidence on one directed
relationship and is not a cycle without a return path. Accept/reject terminals
have no outgoing transition by construction. Unreachable-state diagnostics are
prefixed P4 structural observation (not verifier proof) and are recomputed
idempotently. Compiler/verifier Flags edges never enter these structural
metrics.
P4 compiler evidence
The qualified p4c adapter accepts only Brick envelope schema 1 for p4c
v1.2.5.16 p4test-regression diagnostics. It creates Finding nodes and Flags
edges with Imported evidence, never Symbolic evidence. Tool version, command
mode, result category/code, exit status, raw result identity, artifact
identity/provenance, explicit model bounds/confidence category, and source
position remain inspectable attributes or provenance.
The adapter first resolves the exact path/kind/name/owner/line P4 entity and
falls back to the matching file with a diagnostic. Unknown categories, escaping
paths, unsupported tool versions/modes, non-null symbolic bounds, and
unqualified symbolic tools fail closed. The complete envelope and real-fixture
contract is in docs/architecture/p4-evidence.md.
Fold integration
The current adapter imports SARIF and native findings JSON. A deeper adapter should map Fold function/block/operation/value fact IDs directly to Brick identities and retain source/binary lanes. Source proved, latent, heuristic, and concrete-witness confidence must remain distinguishable. Binary evidence must not be promoted to symbolic evidence without corroboration.
A Fold finding with no backend is diagnosed and not imported. Missing confidence is preserved as unknown with a diagnostic. Duplicate finding IDs merge report provenance into the existing Flags relationship.