# 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](structural-diff.md). `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 `null` when 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.