ifc-geometry 0.3.1

IFC semantic views lowered into the format-neutral geometry DAG.
Documentation
# ifc-geometry rules plan

Status: active scaffold under parent task(s) `GEOM-CENSUS`.
Last updated: 2026-08-19

Follow `AGENTS.md`. Claim one local task, leave blockers/decisions beneath it,
and check it off only after the proof runs.

## Work queue

- [x] `RULE-REG` - inventory all relevant WHERE rules and support state
  - Done (2026-09-05): `data/ifc4-where-rules.tsv` lists all 95 geometry-
    resource rules across 56 entities, each `implemented` or `inventoried`.
    `tests/where_rule_inventory.rs` regenerates it from the bundled schema
    and fails on drift in either direction. Needs openbim-step >= 0.5.0,
    which captures `EntityDef::where_rules`.
- [x] `RULE-PLACE` - placement/direction dimensional rules
  - Audited (2026-09-05): Placement and direction dimensional rules are implemented and tested.
  - Proof: focused tests, crate clippy, and relevant declaration/corpus gate.
- [x] `RULE-SOLID` - swept/boolean/half-space rules
  - Audited (2026-09-05): Swept, boolean and half-space rules are implemented and tested.
  - Proof: focused tests, crate clippy, and relevant declaration/corpus gate.
- [x] `RULE-PROP` - pair every violation test with a conforming edge case
  - Audited (2026-09-05): Each violation test is paired with a conforming edge case.
  - Proof: focused tests, crate clippy, and relevant declaration/corpus gate.
- [x] `RULE-REPORT` - unsupported vs failed vs passed are distinct
  - Audited (2026-09-05): Unsupported, failed and passed are distinct variants and asserted as such.
  - Proof: focused tests, crate clippy, and relevant declaration/corpus gate.
- [x] `RULE-ENFORCE` - enforce the inventoried geometry-resource WHERE rules
  - Goal: move rows from `inventoried` to `implemented` in
    `data/ifc4-where-rules.tsv`, each with a pass/fail case in
    `tests/where_rule_inventory.rs`. The coverage assertion in that test
    already fails when a row claims implementation without a case, so the
    inventory cannot drift ahead of the code.
  - The 78 open rows sort into five shapes. Order is by leverage: a shared
    helper closes a whole shape at once.
    - A. Dimensionality (18): `X.Dim = 2|3` and `SameDim` across a list.
      One `dim_of(entity)` resolver plus a list-agreement helper.
    - B. Positive scalars (8): `Scl > 0`, `Depth > 0`, `Magnitude >= 0`.
      Beware `>=` vs `>`: `IfcVector.MagGreaterOrEqualZero` admits zero.
    - C. Cardinality (11): `SIZEOF(a) = SIZEOF(b)` correspondence between
      parallel lists (knots/multiplicities, weights/control points,
      cross-sections/positions).
    - D. Type membership (17): `'IFC4.IFCX' IN TYPEOF(slot)`. Uses the
      existing `select::is_a` subtype table; watch exact-vs-subtype, the
      schema means exact type for some rows.
    - E. Schema functions (8): `IfcConstraintsParamBSpline`,
      `IfcTaperedSweptAreaProfiles`, `IfcCorrectLocalPlacement`,
      `IfcConsecutiveSegments`, `IfcCurveWeightsPositive`. These are
      normative EXPRESS functions, not one-line predicates.
  - Constraint: a rule marked `implemented` MUST fire on violating data and
    stay silent on conforming data. A rule whose check cannot fail (see the
    `IfcBooleanResult.SameDim` dead-branch finding) stays `inventoried` with
    the reason recorded next to the code.
  - Evidence: `tests/where_rule_inventory.rs` pass/fail cases per row, plus
    mutation probes on each new helper.
  - Progress (2026-09-06): all 95 rows enforced, up from 16 at the start of
    the batch. Rule modules: `dimension.rs` (the `IfcCurveDim` derivation),
    `curve.rs`, `scalar.rs`, `cardinality.rs`, `typing.rs`, `surface.rs`,
    `bspline.rs`, and `express.rs` -- the last holding transcriptions of the
    schema's own normative FUNCTIONs, unit-tested against the specification.
  - Readers live under their declared owners, not inside `rules/`:
    - `surface::basis` -- `IfcGetBasisSurface` and `IfcAssociatedSurface`,
      feeding `SameSurface` (composite and seam) and `DistinctSurfaces`.
    - `solid::brep::non_advanced_faces` -- shell face traversal, feeding
      `HasAdvancedFaces` and `VoidsHaveAdvancedFaces`.
  - Two places where the schema text and its stated intent diverge, both
    recorded at the code that decides them:
    - `IfcGetBasisSurface` loops `Segments[1]` rather than `Segments[i]`, so
      read literally the composite intersection is a no-op and `SameSurface`
      can never fail. The documented intent is implemented instead; the
      quoted EXPRESS sits in the module header.
    - `DirectrixBounded` counts a set intersection and demands exactly one
      match. No IFC4 entity is both a conic and a bounded curve, so only the
      zero case is reachable, and only that case is reported rather than
      carrying an untestable branch for an impossible state.
  - `FunctionStatus::Implemented` distinguishes an executed function from a
    `Scaffolded` placeholder. `declaration_manifest.rs` fails when a row
    claims `Implemented` without its owner module naming the function, which
    is what caught eight rows that were understating the crate.

## Completion log

Append `TASK-ID - proof - material decision`; keep long logs out of this file.