Skip to content

Contributing ​

Before changing code ​

  1. Read AGENTS.md.
  2. Keep IFCX independent of openbimrs/ifc internals, and never make ifc depend on this repository.
  3. Name the IFCX draft revision that a capability targets.
  4. Do not commit buildingSMART schemas or samples without verified redistribution rights.

Verification ​

Run the complete gate from the repository root:

bash
./scripts/gate.sh

The command must exit successfully. It runs two sections, which CI runs as parallel jobs: rust (format, tests, clippy, rustdoc, the generated docs and the docs site build, packaging) and bindings (the npm package built with the pinned wasm-bindgen CLI and tested under Node; the Python wheel built with maturin, installed into a uv venv, and tested). Run one with ./scripts/gate.sh rust. The bindings need wasm-bindgen-cli 0.2.128, Node, uv and maturin on PATH; a missing tool fails the gate and names what to install. IFCX_SKIP_JS=1 or IFCX_SKIP_PYTHON=1 skips a suite locally with a warning; CI never sets them. Update README, rustdoc, capabilities, and CHANGELOG together when a user-visible contract changes.

Documentation ​

The documentation site, https://openbimrs.github.io/ifcx/, is built with VitePress from docs/, with the rustdoc of every crate under /api/rustdoc/ and the viewer under /viewer/. Everything the site states that can be derived from the source is generated by the xtask crate, and the gate fails when a generated page or region is out of date:

bash
cargo run -p xtask -- docs           # regenerate
cargo run -p xtask -- docs --check   # what the gate runs

Edit the source, never the generated output:

On the siteEditGenerated into
Crate reference pagesthe crate's Cargo.toml, README.md, CHANGELOG.md, crate docs and public APIdocs/reference/
JavaScript and Python API tablescrates/openbim-ifcx-wasm/src/api.rs, js/fetch-imports.d.ts; crates/openbim-ifcx-py/python/openbim_ifcx/__init__.pythe binding reference pages
Changelogthe root and per-crate CHANGELOG.md filesdocs/project/changelog.md
Contributing, Releasingthis filedocs/project/contributing.md, releasing.md
The viewerdemo/README.mddocs/guide/viewer.md
ADR index and sidebardocs/adr/NNNN-*.md (# NNNN — Title, - **Status:**)docs/adr/index.md, docs/.vitepress/data/adrs.json
Upstream evidencescripts/upstream-drift/baseline.txtdocs/evidence.md
Install table, rustdoc list, README crate tablecrate manifests and changelogs<!-- NAME:BEGIN --> regions of docs/guide/install.md, docs/api/index.md, README.md

Prose outside the <!-- NAME:BEGIN --> / <!-- NAME:END --> markers is written by hand, as are the home page, the guides, docs/capabilities.md and the ADRs. The code on the guide pages is imported from #region blocks of tests that the gate runs: crates/openbim-ifcx-geometry/tests/guide.rs, crates/openbim-ifcx-wasm/tests/js/guide.mjs and crates/openbim-ifcx-py/tests/python/test_guide.py. Change the code there. Every crate keeps its own README.md and CHANGELOG.md; the docs check fails without them.

To preview the site, install the pinned toolchain (package.json, package-lock.json) once, then:

bash
npm ci
npm run docs:dev      # live preview of docs/ at http://localhost:5173/ifcx/
npm run docs:build    # what the gate runs when node_modules exists

The preview has neither rustdoc nor the viewer; .github/workflows/pages.yml adds both when it deploys main. Mermaid diagrams need accTitle and accDescr (docs/scripts/check-diagrams.mjs). Nothing from buildingSMART/IFC5-development may be published: scripts/check-leakage.py checks the source tree in the gate and the built site, against an upstream checkout, before every deployment.

Upstream drift ​

IFCX is an alpha draft. The evidence in docs/capabilities.md was recorded against a pinned buildingSMART/IFC5-development commit, and .github/workflows/upstream-drift.yml re-checks it against upstream's default branch every Monday at 04:23 UTC. It runs scripts/upstream-drift.sh, which fetches the ifcx.dev imports into an offline mirror and runs every opt-in upstream check: round trip, validation, composition, layer stacks, geometry decode, render scenes, composition parity with upstream's TypeScript, and the Khronos glTF validator. Each check's summary line (counts and failures, without timings) is compared with scripts/upstream-drift/baseline.txt.

When it finds drift, a failed check or a changed summary line, it opens an issue labelled upstream-drift, a sub-issue of #40. If one is already open it updates that issue and comments, so there is never more than one. The issue names the upstream commit and the failed checks, and links:

  • the run and its Upstream checks job log, where each check is a collapsible group;
  • the upstream-drift-report artifact, with logs/<check>.log per check, report.md, summary.txt, and baseline.diff.

Start from baseline.diff and the failed check's log, then reproduce locally against the same commit:

bash
git -C ../IFC5-development fetch && git -C ../IFC5-development checkout <commit>
IFCX_UPSTREAM_DIR=../IFC5-development ./scripts/upstream-drift.sh

Then either fix the crate, if upstream changed the draft or found a bug, or accept the new results: update the counts and the named revision in docs/capabilities.md, run ./scripts/upstream-drift.sh --update-baseline, and commit both. The first scheduled or dispatched run against the default branch that passes closes the issue. Known gaps are part of the baseline; for example, ifcx.dev does not serve ifc-mat/prop@v1.0.0.ifcx (upstream #124), so 4 examples have an unresolved import. If upstream fixes that, the baseline differs and the issue says so.

The run summary also lists the upstream branches (draft work such as post-alpha happens there) that moved, appeared, or disappeared since the last recorded run. A moved branch alone opens no issue. The record is the upstream-drift-state artifact (branch heads and the checked revision, kept 90 days) of the latest non-dry run on main.

Run it by hand from Actions -> Upstream drift -> Run workflow, or:

bash
gh workflow run upstream-drift.yml -R openbimrs/ifcx --ref main
gh workflow run upstream-drift.yml -R openbimrs/ifcx --ref main \
  -f upstream_ref=post-alpha -f dry_run=true

upstream_ref checks a branch, tag, or full commit SHA instead of the default branch. dry_run only writes the run summary: no issue is opened, updated, or closed, and no state is recorded.

Fuzzing ​

fuzz/ holds cargo-fuzz targets for the reader, composition, validation, and the geometry decoders. It is its own Cargo workspace with its own Cargo.lock, outside the root workspace and the MSRV gate, because libFuzzer needs a nightly toolchain; fuzz/rust-toolchain.toml pins the date.

TargetExercises
readIfcxFile::from_json_slice on arbitrary bytes; a file that reads must write and read back equal; then validate, flatten, and compose
composeflatten + compose over IFCX JSON or structured node lists with colliding paths, cycles, diamonds, and head/child edits
validateIfcxFile::validate with schemas that inherit each other (chains, diamonds, cycles) and nested restrictions
pcdPointCloud::from_pcd on arbitrary bytes (ascii, binary, binary_compressed), checked against from_pcd_base64
pcd_lzfthe binary_compressed LZF decoder and column-major layout behind a fixed valid header
points_base64points::base64 strings and points::array JSON
geometrymesh, curve, transform, presentation, and point-array decoders on one JSON value
scene_glbRenderScene and to_glb on composed files; checks the GLB structure, alignment, index ranges, and that the JSON holds no null

Install cargo-fuzz once (cargo install cargo-fuzz), then run a target for a bounded time:

bash
fuzz/run.sh --list              # target names
fuzz/run.sh pcd 600             # ten minutes
fuzz/run.sh compose 60 -jobs=4  # extra libFuzzer flags after the seconds

fuzz/run.sh builds with the pinned nightly, debug assertions (so integer overflow panics), and ASan; reads seeds from fuzz/seeds/<target> and, for targets taking IFCX files, from the hand-written fixtures in crates/*/tests/fixtures; writes new inputs to fuzz/corpus/<target>; and limits memory to 2 GiB and each input to 10 s, so unbounded allocation and super-linear blow-ups are findings too. Plain cargo +nightly-2026-09-25 fuzz run <target> works as well.

A crash leaves its input in fuzz/artifacts/<target>/. Fix it in the library crate with a regression test in that crate's normal test suite (never only in fuzz/), and add the minimized input (cargo fuzz tmin <target> <file>) to fuzz/seeds/<target>/ when it is a useful seed. Seeds must be original work: never add buildingSMART samples.

CI runs every target in .github/workflows/fuzz.yml: one minute each on pull requests that touch the fuzzed crates or fuzz/, ten minutes each every Monday (keeping the corpus between weekly runs), and on demand with gh workflow run fuzz.yml -R openbimrs/ifcx -f seconds=1800. That workflow is not part of the required gate. When bumping the nightly date, change fuzz/rust-toolchain.toml and the workflow together.

Released under the MIT licence. No buildingSMART files or texts are published here.