# Research Notes This document covers the research behind ifc-commit's approach to storing git provenance inside IFC files: the mechanisms surveyed, the tradeoffs considered, and how the implementation was designed. --- ## Embedding Commit History in IFC Files A survey of IFC mechanisms for storing git commit metadata. --- ### 1. IfcOwnerHistory — The Native Mechanism Every `IfcRoot`-derived entity (walls, spaces, products, etc.) carries an optional `IfcOwnerHistory` attribute. It is the closest thing IFC has to built-in change tracking. **Fields:** | Field | Type | Notes | |-------|------|-------| | `OwningUser` | `IfcPersonAndOrganization` | Who created the element | | `OwningApplication` | `IfcApplication` | Software used | | `State` | `IfcStateEnum` | `READWRITE`, `READONLY`, `LOCKED` | | `ChangeAction` | `IfcChangeActionEnum` | `ADDED`, `MODIFIED`, `DELETED`, `NOCHANGE` | | `LastModifiedDate` | `IfcTimeStamp` | Unix timestamp | | `LastModifyingUser` | `IfcPersonAndOrganization` | | | `LastModifyingApplication` | `IfcApplication` | | | `CreationDate` | `IfcTimeStamp` | Unix timestamp | Raw IFC line from `samples/duplex.ifc`: ``` #33=IFCOWNERHISTORY(#32,#2,$,.NOCHANGE.,$,$,$,0); ``` **Limitations:** - Only the *current* state — no history chain - `ChangeAction` is a coarse enum; no room for a commit hash, message, or branch - `IfcApplication.Version` is a short string, not designed for structured data - One record per element; previous owners are lost on update **Verdict:** Good for standard compliance and timestamping. Not sufficient alone for git metadata. --- ### 2. IfcPropertySet — The Recommended Extension Point Custom property sets (`Pset_*`) are the standard IFC way to attach arbitrary key-value metadata to any `IfcObject`. They survive round-trips through most IFC-aware tools (unknown Psets are ignored, not discarded). > **Schema** — `Pset_GitCommit` | Property | Type | Example | |----------|------|---------| | `CommitHash` | `IfcLabel` | `a1b2c3d4f5e6c7b8` | | `CommitMessage` | `IfcText` | `Fix wall thickness` | | `CommitAuthor` | `IfcLabel` | `alice <***@***>` | | `CommitDate` | `IfcLabel` | `2026-03-24T14:30:00Z` | | `CommitBranch` | `IfcLabel` | `main` | | `OperationName` | `IfcLabel` | `Merge` | **ifcopenshell snippet — writing:** ```python pset = ifcopenshell.api.pset.add_pset(model, product=element, name="Pset_GitCommit") ifcopenshell.api.pset.edit_pset(model, pset=pset, properties={ "CommitHash": commit_hash, "CommitMessage": commit_message, "CommitAuthor": commit_author, "CommitDate": commit_date, "CommitBranch": branch, "OperationName": operation_name, }) ``` **Reading back:** ```python for rel in element.IsDefinedBy or []: if rel.is_a("IfcRelDefinesByProperties"): pset = rel.RelatingPropertyDefinition if pset.Name == "Pset_GitCommit": props = {p.Name: p.NominalValue.wrappedValue for p in pset.HasProperties} ``` **Verdict:** Best fit for per-element traceability. Flexible, queryable, spec-compliant. --- ### 3. IfcDocumentInformation — For Linking to External Commits `IfcDocumentInformation` + `IfcRelAssociatesDocument` lets you attach a document reference (URL, identifier, description) to any `IfcRoot` entity. It can carry a git commit URL back to the forge. ```python doc = ifcopenshell.api.document.add_information(model) ifcopenshell.api.document.edit_information(model, information=doc, attributes={ "Identification": commit_hash[:8], "Name": commit_message, "Location": f"https://gitaec.org/rvba/ifc-commit/commit/{commit_hash}", }) ref = ifcopenshell.api.document.add_reference(model, information=doc) ifcopenshell.api.document.assign_document(model, products=[element], document=ref) ``` **Verdict:** Useful for linking elements to a hosted commit URL. More verbose than a Pset. Better suited for file-level "source revision" than per-element tracking. --- ### 4. IfcApplication — File-Level Commit Stamp `IfcApplication` is referenced by every `IfcOwnerHistory`. Its `Version` field can carry the current commit hash as a lightweight file-level stamp. ```python app = ifcopenshell.api.owner.add_application(model) ifcopenshell.api.owner.edit_application(model, application=app, attributes={ "ApplicationIdentifier": "ifc-commit", "Version": commit_hash, "Name": "ifc-commit", }) ``` **Verdict:** Zero overhead. Limited to one hash per file. Good as a quick "what commit produced this file" marker. --- ### 5. Comparison | Mechanism | Granularity | Stores hash/message | IFC compliance | Overhead | |-----------|-------------|---------------------|----------------|----------| | `IfcOwnerHistory` | per-element | No | Native | Minimal | | `Pset_GitCommit` | per-element | Yes (all fields) | Standard extension | Medium | | `IfcDocumentInformation` | per-element | Yes (via Location) | Standard | High | | `IfcApplication.Version` | per-file | Hash only | Native | Minimal | --- ### 6. Adopted Approach A two-layer design: 1. **File level** — `IfcApplication.Version` is set to the commit hash. Every tool that reads `IfcOwnerHistory` exposes this with no extra work. 2. **Element level** — On elements touched by an operation, a `Pset_GitCommit` property set is written with the full commit metadata. `IfcOwnerHistory.ChangeAction` is updated to `ADDED` or `MODIFIED` accordingly. This keeps standard IFC compliance intact while making full git provenance queryable directly from the model. --- ## Implementation Plan The following section describes how the history mechanism is integrated into the pipeline and webapp. --- ### Pipeline Integration The `history` command in ifc-commit operates in two modes: - **Write mode** (`write_psets: true`): at the end of a pipeline run, stamps `Pset_GitCommit` on all elements in every operation's output IFC, using the current `HEAD` commit. - **Read mode** (`input` present): opens an IFC file, collects all `Pset_GitCommit` records, and writes them to a JSON file for the webapp. **Example pipeline declaration:** ```yaml operations: - name: Extract command: extract input: ifc/duplex.ifc output: ifc/duplex_extract.ifc type: IfcSpace id: A102 - name: Modify command: modify input: ifc/duplex_extract.ifc output: ifc/duplex_modified.ifc element: "168381" x: 2 - name: Merge command: merge base: ifc/duplex.ifc space: A102 part: ifc/duplex_modified.ifc output: ifc/duplex_merge.ifc - name: WriteHistory command: history write_psets: true - name: ReadHistory command: history input: ifc/duplex_merge.ifc output: ifc/history.json ``` --- ### Webapp Integration After a pipeline run, the webapp calls `/api/ifc-history` to surface the per-element commit metadata, with links back to the corresponding commits on the forge. Each element that was touched by the pipeline carries: > **Schema** — per-element provenance | Property | Example | |----------|---------| | `CommitHash` | `a1b2c3d4…` | | `CommitMessage` | `move table` | | `CommitAuthor` | `rvba <***@***>` | | `CommitDate` | `2026-03-24T14:30:00Z` | | `CommitBranch` | `main` | | `OperationName` | `Modify` | The element history panel links each commit hash to its page on the forge, making the full modification trail navigable directly from the webapp. --- ## Related Work - [ifc-data-bus](https://github.com/vyzn-tech/ifc-data-bus) - [ifc-data-horse](https://gitaec.org/rvba/ifc-data-horse) - [buildingSMART Hackathon 2026](https://github.com/lfcastel/buildingSMART-Hackathon-2026) - [Git-based IFC — Bruno Postle](https://gitaec.org/brunopostle/creative-freedom)