ripbi-core 0.3.4

Static analysis engine for Power BI semantic models: TMDL and PBIR ingestion, DAX reference extraction, dependency graph, and reachability
Documentation
# ripbi-core — CONTEXT

Pure library crate: format ingestion, DAX/M lexing, and dependency-graph analysis.
No I/O to stdout/stderr, no `std::process::exit` — everything returns
`Result<T, ripbi_core::Error>`. `#![forbid(unsafe_code)]`.

## Responsibilities

- **Semantic model** (`src/model.rs`, `src/identity.rs`): the normalized `TabularDatabase`
  AST and the case-insensitive object identity every other layer refers to objects by.
  Columns carry their `variations` (the date-variation declarations report bindings
  resolve through); tables carry the auto date/time identity flags. Pure data — no
  parsing, no I/O.
- **Report** (`src/report.rs`): the normalized `ReportModel` AST — pages, visuals,
  filters, bookmarks, and report-level measures. Its `bindings()` enumeration is the
  reachability-root surface; pure data, populated by the ingestion formats.
- **Ingestion** (`src/ingest/`): detect and parse the four source formats —
  legacy `.pbix`/`.pbit` (unzip in-memory, `DataModelSchema` + `Report/Layout`),
  `.pbip` folders (`.Report/` PBIR + `.SemanticModel/` TMDL), standalone TMDL
  folders, and TMSL `model.bim`. All normalize into `TabularDatabase` + `ReportModel`.
- **DAX lexing** (`src/dax/`): tokenize DAX expressions to extract object references
  (fields, bare table uses, function calls) and resolve them against the model as data.
  Zero-copy `&str` tokens; a conservative port of SQLBI Whiteboard's lexer.
- **M lexing** (`src/m/`): tokenize Power Query (M) expressions — table partitions and
  shared expressions — to extract the columns, tables, and shared expressions they
  name. A column named in M is that column's supply chain, never a consumer (unloading
  it cannot break refresh); tables and shared expressions named in M do keep alive,
  because deleting them deletes a query another expression reads (issue #39). Same
  zero-copy, total-lexer, conservatism-first design as the DAX side.
- **Graph** (`src/graph/`): build a DAG with `petgraph`, BFS reachability from report
  roots (visuals, filters, slicers) to isolate dead objects, plus the provenance-based
  auto-date/time verdict (`DependencyGraph::auto_date_time_tables`) read off the same
  graph, and the broken-binding records (`graph/broken.rs`,
  `DependencyGraph::broken_bindings`) — written references that resolve to nothing, or
  onto a broken artifact (issue #60). The precision bar inverts the conservatism rule:
  liveness may over-keep, breakage must under-claim.

## Rules

- Handle Power BI schema drift gracefully — unknown JSON fields must never panic.
- Ingestion output must be format-agnostic: downstream code never branches on source format.
- Module files sit *beside* their directory (`src/model.rs` + `src/model/`), not inside it
  as `mod.rs`. `ingest`, `dax`, `m`, and `graph` follow the same shape — with five modules a
  crate full of `mod.rs` tabs is unnavigable.

## Routing

| Task | Read | Notes |
|---|---|---|
| Semantic model AST | [docs/semantic-model.md]docs/semantic-model.md | What is modeled and what is not; where DAX hides. Read before changing the AST |
| Report AST & root bindings | [docs/report-model.md]docs/report-model.md | What binds model objects and why; where roots hide. Read before changing the report AST |
| Name resolution & identity | [docs/name-resolution.md]docs/name-resolution.md | Case-insensitivity, model-global measures, why `[Name]` resolves to *all* candidates |
| Format specs & detection | [docs/formats.md]docs/formats.md | What each format's parser accepts, the TMDL grammar subset, the PBIR key policies, and the drift/skip policy. Read before changing any ingest module |
| DAX token grammar | [docs/dax-lexing.md]docs/dax-lexing.md | Token grammar, extraction rules, the conservatism rule, and the SQLBI provenance. Read before changing the lexer |
| M token grammar | [docs/m-lexing.md]docs/m-lexing.md | The M lexer/extraction/binding rules, the column-string function whitelist, and what is deliberately not modeled. Read before changing the `m` module |
| Graph, reachability & liveness policy | [docs/graph.md]docs/graph.md | The edge catalog, the two-pass relationship rule, and what "unused" means. Read before changing the graph module |
| Broken bindings | [docs/graph.md]docs/graph.md, [docs/name-resolution.md]docs/name-resolution.md | The `broken_visual` records: what counts as broken, what under-claims (KPI variants, variation hierarchies, calculated-table expressions, `Written`), and the artifact-breakage pass #84 will promote. Read `graph/broken.rs` before touching resolution-miss handling |