Skip to content

feat(models): cpg — canonical schema-v2 models, modeled once (F2 / #240) - #274

Merged
rahlk merged 12 commits into
release/2.0from
feat/issue-240-cpg-models
Jul 15, 2026
Merged

feat(models): cpg — canonical schema-v2 models, modeled once (F2 / #240)#274
rahlk merged 12 commits into
release/2.0from
feat/issue-240-cpg-models

Conversation

@rahlk

Copy link
Copy Markdown
Collaborator

cpg models — canonical schema-v2, modeled once (F2 / #240)

Closes #240. Adds cldk/models/cpg/ — Layer 1 of the two-layer model: the schema-v2 Pydantic models, modeled ONCE, that parse the analysis.json of every CLDK analyzer. Part of epic #238 (2.0.0-rc.1). Unblocks F7 Tasks 7–10 (#270) and F3 views (#241), which read these models directly.

What's here

  • base.py_NullSafeBase: extra="allow" (one model set tolerates every language's extras — the opposite of the old per-language extra="forbid") + a null-drop validator (coerces null collections to defaults for Go/Rust/C).
  • models.pySpan (byte offsets), Edge ({src,dst,kind?,var?,prov,weight} — the list-name is the type, no CALL_DEP field), Import, open-kind Node (one model over type/callable/field/body facets, self-referential, kind:str), Module (+source, +span), Application, Analyzer, AnalysisPayload (envelope; max_level authoritative).
  • The join-key invariant enforced positionally: durable nodes (in types/functions/callables/fields) require an id; body nodes (keyed by local position in body{}) may omit it — via container validators, not a kind-list (respects open-kind).

Validation — the point of the layer

30 tests. Parses REAL, conformant --eager output from BOTH analyzers (codeanalyzer-python + cants/TypeScript) at L1 and L4 (4 committed golden fixtures), holds the L1 ⊆ L4 superset gate, and pins the exact accessor surface F7's Task-7 provider and F3's views consume — with discriminating guards (under extra="allow", a naive isinstance can't detect a removed field, so the accessor tests dereference past the container and check model_extra membership).

Scope / safety

Purely additive — a new package, no imports into any existing facade; the frozen per-language models/facades are untouched.

Built via subagent-driven TDD with two-stage review; the gate caught 8 defects the passing tests didn't reveal

Including a too-broad Node.id relaxation that silently voided the keystone join-key invariant for durable nodes, a short-circuited accessor assertion giving false coverage, and (whole-branch) Module.span degrading to a raw extra + extra="allow" making parse-tests non-probative for field presence.

Known follow-ups (do NOT block this merge — filed)

  • #273 — canonical vs analyzer vocabulary drift (BLOCKS F3, not F2): the models are keystone-faithful, but the analyzers emit off-keystone names (base_classesbase_types, attributesfields, cyclomatic_complexitymetrics.cyclomatic), so those canonical fields parse EMPTY on real data. Analyzer-side conformance / keystone reconciliation — must be resolved before F3 maps those fields. #273 also folds in the nested-types question (keystone uses flat type.nesting, not a Node.types container).
  • Fixture hygiene on next regeneration: scrub machine-absolute paths in file_path extras, pretty-print, make py-a1 a pure L1 exemplar; switch next(iter(...)) selectors to named keys.

rahlk added 12 commits July 14, 2026 21:12
span is listed as a common field on every node in the keystone (Part
II), module included, but Module had no span field so it degraded to
a raw dict in model_extra instead of parsing as Span. The ts-a4/ts-a1
fixtures emit span on the module node.
…ct (#240)
extra="allow" absorbs unknown keys, so deleting a canonical field
leaves every parse test green — the accessor contract is the only
guard. cdg and summary (both in F7's cfg/cdg/ddg/summary read set),
the envelope k_limit, and TS callable body/cfg were unpinned.
Each new assertion dereferences an element/field rather than doing a
bare isinstance check, since a raw dict-of-dicts under extra="allow"
still satisfies isinstance(list)/isinstance(dict) — confirmed by
temporarily removing each field from the model and watching the new
asserts fail before restoring.
@rahlkrahlk added the kind/feature New feature(s) label Jul 15, 2026
@rahlk
rahlk merged commit 46dfd5a into release/2.0Jul 15, 2026
@rahlk
rahlk deleted the feat/issue-240-cpg-models branch July 15, 2026 04:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/featureNew feature(s)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rahlk