rvt 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. rvt-0.1.2/CHANGELOG.md +344 -0
  2. rvt-0.1.2/CITATION.cff +32 -0
  3. rvt-0.1.2/CODE_OF_CONDUCT.md +67 -0
  4. rvt-0.1.2/CONTRIBUTING.md +137 -0
  5. rvt-0.1.2/Cargo.lock +792 -0
  6. rvt-0.1.2/Cargo.toml +113 -0
  7. rvt-0.1.2/LICENSE +201 -0
  8. rvt-0.1.2/NOTICE +80 -0
  9. rvt-0.1.2/PKG-INFO +350 -0
  10. rvt-0.1.2/README.md +327 -0
  11. rvt-0.1.2/SECURITY.md +63 -0
  12. rvt-0.1.2/docs/benchmarks.md +59 -0
  13. rvt-0.1.2/docs/corpus-sources.md +77 -0
  14. rvt-0.1.2/docs/data/bench-2024.md +9 -0
  15. rvt-0.1.2/docs/data/tag-drift-heatmap.svg +1485 -0
  16. rvt-0.1.2/docs/python.md +198 -0
  17. rvt-0.1.2/docs/rvt-moat-break-reconnaissance.md +1774 -0
  18. rvt-0.1.2/docs/rvt-phase4c-session-2026-04-19.md +259 -0
  19. rvt-0.1.2/docs/rvt-python-quickstart.ipynb +224 -0
  20. rvt-0.1.2/examples/adocument_bruteforce.rs +182 -0
  21. rvt-0.1.2/examples/adocument_entry.rs +161 -0
  22. rvt-0.1.2/examples/adocument_walk.rs +198 -0
  23. rvt-0.1.2/examples/adocument_walker_v1.rs +358 -0
  24. rvt-0.1.2/examples/contents_probe.rs +99 -0
  25. rvt-0.1.2/examples/directory_bytewise_scan.rs +248 -0
  26. rvt-0.1.2/examples/directory_class_lookup.rs +150 -0
  27. rvt-0.1.2/examples/directory_probe.rs +131 -0
  28. rvt-0.1.2/examples/directory_vs_elemtable.rs +214 -0
  29. rvt-0.1.2/examples/elem_table_probe.rs +105 -0
  30. rvt-0.1.2/examples/elem_table_sanity.rs +66 -0
  31. rvt-0.1.2/examples/field_type_probe.rs +300 -0
  32. rvt-0.1.2/examples/flag_word_probe.rs +143 -0
  33. rvt-0.1.2/examples/instance_scan.rs +100 -0
  34. rvt-0.1.2/examples/link_schema.rs +119 -0
  35. rvt-0.1.2/examples/partition_diff.rs +34 -0
  36. rvt-0.1.2/examples/partition_full.rs +99 -0
  37. rvt-0.1.2/examples/partition_invariant.rs +145 -0
  38. rvt-0.1.2/examples/partitions_header_probe.rs +112 -0
  39. rvt-0.1.2/examples/partitions_q7.rs +92 -0
  40. rvt-0.1.2/examples/post_directory.rs +145 -0
  41. rvt-0.1.2/examples/post_table_b_density.rs +164 -0
  42. rvt-0.1.2/examples/post_table_b_head.rs +116 -0
  43. rvt-0.1.2/examples/probe_link.rs +67 -0
  44. rvt-0.1.2/examples/record_framing.rs +179 -0
  45. rvt-0.1.2/examples/roundtrip.rs +36 -0
  46. rvt-0.1.2/examples/second_table_probe.rs +92 -0
  47. rvt-0.1.2/examples/table_b_structure.rs +152 -0
  48. rvt-0.1.2/examples/tag_bytes.rs +34 -0
  49. rvt-0.1.2/examples/tag_drift.rs +220 -0
  50. rvt-0.1.2/examples/tag_drift_svg.rs +177 -0
  51. rvt-0.1.2/examples/tag_dump.rs +116 -0
  52. rvt-0.1.2/examples/unknown_bytes.rs +59 -0
  53. rvt-0.1.2/examples/unknown_bytes_deep.rs +71 -0
  54. rvt-0.1.2/examples/write_roundtrip.rs +79 -0
  55. rvt-0.1.2/pyproject.toml +41 -0
  56. rvt-0.1.2/pytest.ini +6 -0
  57. rvt-0.1.2/python/rvt/__init__.py +28 -0
  58. rvt-0.1.2/python/rvt/__init__.pyi +161 -0
  59. rvt-0.1.2/python/rvt/py.typed +0 -0
  60. rvt-0.1.2/src/basic_file_info.rs +202 -0
  61. rvt-0.1.2/src/bin/rvt_analyze.rs +891 -0
  62. rvt-0.1.2/src/bin/rvt_corpus.rs +122 -0
  63. rvt-0.1.2/src/bin/rvt_diff.rs +171 -0
  64. rvt-0.1.2/src/bin/rvt_doc.rs +175 -0
  65. rvt-0.1.2/src/bin/rvt_dump.rs +118 -0
  66. rvt-0.1.2/src/bin/rvt_history.rs +209 -0
  67. rvt-0.1.2/src/bin/rvt_ifc.rs +64 -0
  68. rvt-0.1.2/src/bin/rvt_info.rs +165 -0
  69. rvt-0.1.2/src/bin/rvt_schema.rs +137 -0
  70. rvt-0.1.2/src/class_index.rs +101 -0
  71. rvt-0.1.2/src/compression.rs +171 -0
  72. rvt-0.1.2/src/corpus.rs +329 -0
  73. rvt-0.1.2/src/elem_table.rs +138 -0
  74. rvt-0.1.2/src/error.rs +31 -0
  75. rvt-0.1.2/src/formats.rs +1033 -0
  76. rvt-0.1.2/src/ifc/entities.rs +59 -0
  77. rvt-0.1.2/src/ifc/mod.rs +203 -0
  78. rvt-0.1.2/src/ifc/step_writer.rs +650 -0
  79. rvt-0.1.2/src/lib.rs +108 -0
  80. rvt-0.1.2/src/object_graph.rs +269 -0
  81. rvt-0.1.2/src/part_atom.rs +199 -0
  82. rvt-0.1.2/src/partitions.rs +169 -0
  83. rvt-0.1.2/src/python.rs +315 -0
  84. rvt-0.1.2/src/reader.rs +253 -0
  85. rvt-0.1.2/src/redact.rs +156 -0
  86. rvt-0.1.2/src/streams.rs +54 -0
  87. rvt-0.1.2/src/walker.rs +428 -0
  88. rvt-0.1.2/src/writer.rs +204 -0
  89. rvt-0.1.2/tests/common/mod.rs +36 -0
  90. rvt-0.1.2/tests/field_type_coverage.rs +122 -0
  91. rvt-0.1.2/tests/ifc_roundtrip.rs +125 -0
  92. rvt-0.1.2/tests/python/conftest.py +6 -0
  93. rvt-0.1.2/tests/python/test_rvt.py +317 -0
  94. rvt-0.1.2/tests/samples.rs +205 -0
rvt-0.1.2/CHANGELOG.md ADDED
@@ -0,0 +1,344 @@
1
+ # Changelog
2
+
3
+ All notable changes will be documented here. This project follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
5
+ [semver](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.2] — 2026-04-19
10
+
11
+ First tagged release since 0.1.0. Bundles the Python bindings,
12
+ document-level IFC4 export, Layer 5a ADocument walker, and the
13
+ spatial-hierarchy / classification extensions that land between
14
+ `v0.1.0` (initial public release) and the PyPI debut. Changelog
15
+ entries previously accumulated under `[Unreleased]` move here
16
+ verbatim.
17
+
18
+ ### Changed — IFC exporter now emits the full spatial hierarchy
19
+
20
+ - **`rvt-ifc` output now includes `IfcSite → IfcBuilding → IfcBuildingStorey`**
21
+ with `IfcLocalPlacement` per container and `IfcRelAggregates`
22
+ binding each level to its parent. Previous output was a valid-but-
23
+ empty `IfcProject`; BlenderBIM and IfcOpenShell-based viewers
24
+ accepted it but couldn't render anything because there was no
25
+ spatial structure for them to attach geometry to. The minimal
26
+ `Default Site / Default Building / Level 1` hierarchy now opens as
27
+ a navigable scene directly. Once the walker surfaces real
28
+ `BasePoint` / `Level` / `Building` records from the Revit file,
29
+ these placeholder names and the zero-elevation storey will be
30
+ replaced with the actual values.
31
+ - **`make_guid(index)` deterministic GUID generator** — replaces the
32
+ constant `random_guid_stub()` placeholder. Emits 22-character
33
+ strings in the IFC-GUID alphabet (`0-9A-Za-z_$`), prefix `0rvtrs`
34
+ + base-64 big-endian-encoded entity index. Every entity in one
35
+ export now has a distinct GUID; identical models produce
36
+ byte-identical STEP output (STEP text diffs now work).
37
+ - **`IfcClassification` + `IfcClassificationReference` +
38
+ `IfcRelAssociatesClassification` emission.** `RvtDocExporter`
39
+ already extracted OmniClass codes from PartAtom (e.g.
40
+ `23.45.12.34`) into `model.classifications`; the STEP writer now
41
+ actually emits them. Each classification source (OmniClass,
42
+ Uniformat, …) gets one `IfcClassification`; each coded item gets
43
+ an `IfcClassificationReference` linked back to its source; the
44
+ project gets one `IfcRelAssociatesClassification` per reference
45
+ binding the code to the root `IfcProject`. BIM consumers that
46
+ track code/category provenance (Solibri, IfcOpenShell
47
+ classification viewer) can now read those codes directly from
48
+ the exported IFC.
49
+ - 7 new unit tests total pinning spatial-hierarchy presence,
50
+ entity counts, GUID alphabet, GUID determinism, per-file GUID
51
+ uniqueness, OmniClass classification emission with items + names
52
+ + edition, and a guard that empty classifications produce no
53
+ classification entities. Existing `ifc_roundtrip` integration
54
+ tests continue to pass across the 11-release corpus.
55
+
56
+ ### Added — Python bindings via pyo3 + maturin
57
+
58
+ - **`rvt` Python package** — `pip install rvt` produces a single wheel
59
+ per OS/arch that works on every Python ≥ 3.8 (via pyo3 `abi3-py38`).
60
+ Pure-Python `rvt` package wraps the compiled `rvt._rvt` extension
61
+ and ships a PEP 561 `py.typed` marker + hand-maintained
62
+ `__init__.pyi` stubs so mypy, pyright, and IDE autocomplete work
63
+ out of the box.
64
+ - **`rvt.RevitFile` class** — Python surface onto `RustRevitFile`.
65
+ Properties: `version`, `original_path`, `build`, `guid`,
66
+ `part_atom_title`. Methods: `stream_names()`,
67
+ `missing_required_streams()`, `schema_summary()`,
68
+ `read_adocument()` (returns a dict with the walker's
69
+ `ADocumentInstance` serialised to native Python types), and
70
+ `write_ifc()` (returns the IFC4 STEP text).
71
+ - **`rvt.rvt_to_ifc(path)`** one-shot helper — equivalent to
72
+ `RevitFile(path).write_ifc()` for callers that just want the IFC
73
+ string and never touch the intermediate object.
74
+ - **`RevitFile.schema_json()`** — returns the full schema as a JSON
75
+ string (parse with `json.loads` to get a dict equivalent to
76
+ Rust's `SchemaTable`). Zero-copy relative to the decoded schema;
77
+ ~1-2 MB per typical Revit family. `schema_summary()` remains the
78
+ cheap counts-only variant. Two new pytest tests cross-check that
79
+ summary counts match `schema_json()`'s full-parse counts and that
80
+ the `ADocument` class (the walker's target) is always present.
81
+ - **`RevitFile.basic_file_info_json()`** — `BasicFileInfo` as JSON
82
+ in one call. Single-call equivalent of the four individual
83
+ getters (`version` / `original_path` / `build` / `guid`) plus
84
+ any future fields. Returns `None` when the stream is unparseable.
85
+ - **`RevitFile.part_atom_json()`** — `PartAtom` as JSON in one
86
+ call. Superset of `part_atom_title` — also carries `id`,
87
+ `updated`, `taxonomies`, `categories`, `omniclass`, and `raw_xml`
88
+ (the original XML for lossless downstream reuse). Returns `None`
89
+ when the stream is absent (common on project `.rvt` files).
90
+ - Two new pytest tests pin `basic_file_info_json` ↔ individual
91
+ getters agreement, and `part_atom_json` ↔ `part_atom_title`
92
+ agreement plus presence of the structural keys.
93
+ - **`RevitFile.read_stream(name)`** — return the raw bytes of an
94
+ OLE stream by name as a Python `bytes` object. Accepts either
95
+ path form (`"/Formats/Latest"` or `"Formats/Latest"`). Raises
96
+ `IOError` for unknown streams. Use `stream_names()` to enumerate
97
+ what's available. Opens up forensic-inspection use cases the
98
+ announcement draft calls out (reading raw bytes without the
99
+ Rust-API dependency). Three new pytest tests pin bytes
100
+ round-trip, path-normalisation equivalence, and
101
+ missing-stream-raises semantics.
102
+ - **CI wheel build matrix** (`.github/workflows/ci.yml` `python-wheel`
103
+ job) — `PyO3/maturin-action@v1` builds a release wheel on Ubuntu,
104
+ macOS, and Windows runners, installs it into the runner's Python,
105
+ runs the pytest integration suite (`tests/python/test_rvt.py`), and
106
+ uploads the wheel as a workflow artifact. Any regression in the
107
+ Python surface fails CI across all three OSes.
108
+ - **38 pytest integration tests** covering module surface, error
109
+ handling on missing / non-CFB files, happy-path reads against every
110
+ one of the 11 corpus releases (2016–2026), cross-version
111
+ `read_adocument` consistency-band checks, and `write_ifc` output
112
+ shape. Gracefully skips with a clear message when
113
+ `_corpus/rac_basic_sample_family` is absent so local runs work
114
+ without LFS fetches.
115
+ - **`docs/python.md`** — full Python API reference (install, quick
116
+ start, tables per method, return shapes, error handling,
117
+ limitations, troubleshooting, contribution notes).
118
+ - **`docs/rvt-python-quickstart.ipynb`** — 15-cell Jupyter notebook
119
+ mirror of `docs/python.md` for anyone who prefers an interactive
120
+ walkthrough.
121
+ - **`.github/workflows/publish.yml`** — PyPI release workflow. Fires
122
+ on tag push (`v*`) or `workflow_dispatch`. Builds wheels on
123
+ Ubuntu / macOS / Windows via `PyO3/maturin-action@v1`, builds the
124
+ sdist on Ubuntu, downloads every artifact into one `dist/`, and
125
+ publishes via `pypa/gh-action-pypi-publish` using PyPI's Trusted
126
+ Publisher flow (OIDC) — no `PYPI_API_TOKEN` secret stored in the
127
+ repo. Supports `workflow_dispatch` with `test-pypi: true` for
128
+ TestPyPI dry runs. Per-tag releases will cover every Python ≥ 3.8
129
+ on mainstream OSes with one wheel each.
130
+
131
+ Design principle: expose only the stable high-level surface
132
+ (metadata, walker-read ADocument, IFC export). The low-level
133
+ byte-pattern / `FieldType` machinery stays in Rust; Python callers
134
+ get dicts and strings, no wrapper types to learn. To rebuild the
135
+ wheel locally: `maturin build --release --features python`.
136
+
137
+ ### Added — Layer 5: first end-to-end `rvt → ifc` pipeline
138
+
139
+ - **`rvt::ifc::step_writer::write_step`** — pure-Rust IFC4 STEP
140
+ serializer. Takes an `IfcModel`, produces spec-valid ISO-10303-21
141
+ text with all required framework entities (IfcPerson,
142
+ IfcOrganization, IfcApplication, IfcOwnerHistory, IfcSIUnit×4,
143
+ IfcUnitAssignment, IfcGeometricRepresentationContext, IfcProject).
144
+ No IfcOpenShell dependency. No `unsafe`. 4 new unit tests pinning
145
+ envelope shape, escaping, and required entities.
146
+ - **`rvt::ifc::RvtDocExporter`** — concrete `Exporter` that
147
+ populates `IfcModel` from a `RevitFile`. Extracts project name
148
+ from PartAtom (falls back to BasicFileInfo path), builds a
149
+ description string from version + id, pulls OmniClass codes into
150
+ `ClassificationSource::OmniClass`.
151
+ - **`rvt-ifc` CLI** — ninth shipped binary. `rvt-ifc input.rfa`
152
+ writes `input.ifc` next to the input. `rvt-ifc -o path input.rfa`
153
+ overrides. `--null` uses the empty-project exporter for
154
+ STEP-writer testing.
155
+
156
+ First end-user deliverable for Layer 5: `cargo run --release --bin
157
+ rvt-ifc -- sample.rfa` produces a ~1 KB IFC4 file that
158
+ IfcOpenShell, BlenderBIM, and buildingSMART validators can read.
159
+ Geometry and per-element entities are pending walker expansion;
160
+ this v1 covers document-level metadata.
161
+
162
+ ### Fixed
163
+ - **Windows CFB stream-name path separator.** `RevitFile::stream_names()`
164
+ returned backslash-separated paths on Windows (`Formats\Latest`)
165
+ because `Path::display()` uses host-native separators. Now
166
+ normalises to forward-slashes across all OSes so
167
+ `has_revit_signature()` and equivalent cross-stream comparisons
168
+ work uniformly. This was the root cause of the Windows-only
169
+ integration-test failures on the 2016 sample.
170
+ - **MSRV compliance.** Removed a `if let ... && ...` let-chain that
171
+ crept in; let-chains require Rust 1.88+ and the crate's MSRV is
172
+ 1.85. Rewrote as nested `if let { if cond { ... } }`.
173
+
174
+ ### Added — Layer 5a walker + rvt-doc CLI
175
+
176
+ - **`src/walker.rs` module** — first end-to-end schema-directed
177
+ instance reader. Exposes `read_adocument(&mut RevitFile) ->
178
+ Result<Option<ADocumentInstance>>` returning `ADocumentInstance {
179
+ entry_offset, version, fields }` where each field is one of
180
+ `InstanceField::{Pointer, ElementId, RefContainer, Bytes}`.
181
+ - **`rvt-doc` CLI** — eighth shipped binary. Dumps ADocument's
182
+ instance fields as human-readable text or machine-readable JSON
183
+ with `--json`. Respects `--redact` for user-path scrubbing.
184
+ - **Cross-version detection** — hybrid entry-point finder that
185
+ combines a sequential-id-table heuristic with a scoring-based
186
+ brute-force fallback. Works on all 11 releases (Revit 2016–2026)
187
+ with five cross-version-consistent bands:
188
+ 2016–17 / 2018 (solo) / 2019–20 / 2021–23 / 2024–26.
189
+ - **`RevitFile::missing_required_streams()`** — diagnostic form of
190
+ `has_revit_signature`. Returns the list of required stream names
191
+ not found in the file, so "signature invalid" errors can point
192
+ at the specific missing stream.
193
+
194
+ ### Research progress
195
+
196
+ - **Q6.3**: refuted Q6.2's "post-history bytes are ADocument"
197
+ hypothesis. The 131-record table at the post-history boundary
198
+ is a multi-table directory, not ADocument's instance.
199
+ - **Q6.4**: directory u16 body values are not cross-stream
200
+ references. Two sequential-id tables (Table A + Table B) exist
201
+ in Global/Latest.
202
+ - **Q6.5-A/B**: post-Table-B region at 0x0f67 (2024) is where
203
+ ADocument's actual instance data lives. 33× class-tag density
204
+ vs uniform-random baseline.
205
+ - **Q6.5-C**: first-pass walker drifts after field 2 because
206
+ Container wire encoding was wrong.
207
+ - **Q6.5-D**: Container wire is two-column `[u32 count][12 × 6B
208
+ ids][u32 count][12 × 6B masks]` = 152 bytes for count=12.
209
+ - **Q6.5-E**: walker reads 8/13 fields cleanly on Revit 2024.
210
+ - **Q6.5-F**: walker reads ADocument on all 11 releases with
211
+ cross-version-byte-identical output within each version band.
212
+
213
+ ## [0.1.1] — 2026-04-19
214
+
215
+ ### Added
216
+ - **CI-enforced 100% schema-field classification.** New integration
217
+ test `tests/field_type_coverage.rs` opens every file in the 11-version
218
+ `rac_basic_sample_family` corpus, parses the schema, and asserts zero
219
+ fields decode to `FieldType::Unknown`. Fails if any release regresses
220
+ or if the corpus is incomplete — no silent-skip. CI job fetches the
221
+ corpus from [phi-ag/rvt](https://github.com/phi-ag/rvt) at build time
222
+ via `actions/checkout@v4` with LFS (rvt-rs does not redistribute the
223
+ Autodesk-owned sample files; see SECURITY.md).
224
+ - `FieldType` enum with 8 variants (`Primitive`, `String`, `Guid`,
225
+ `ElementId`, `ElementIdRef`, `Pointer`, `Vector`, `Container`) —
226
+ classifies **100.00% of all 13,570 schema fields** across the 11-version
227
+ reference corpus (Revit 2016–2026). Zero fields decode to `Unknown`.
228
+ Evidence: `examples/unknown_bytes_deep.rs` against every sample file.
229
+ - `ClassEntry.tag`, `.parent`, `.ancestor_tag`, `.declared_field_count`,
230
+ `.was_parent_only` — richer schema metadata with cross-release stability.
231
+ - `writer::write_with_patches` + `StreamPatch` / `StreamFraming` types —
232
+ stream-level modifying writer; verified end-to-end round-trip on
233
+ `Formats/Latest`.
234
+ - `compression::truncated_gzip_encode` + `truncated_gzip_encode_with_prefix8`
235
+ — inverse of `inflate_at`, producing Revit-compatible gzip bytes.
236
+ - `redact` module with `redact_path_str` + `redact_sensitive` —
237
+ shared PII scrubber used by every CLI's `--redact` flag.
238
+ - `rvt-analyze` CLI — one-shot forensic analysis. 7 subsystems: identity,
239
+ history, format anchors, schema, schema→data link, content metadata,
240
+ disclosure scan. `--json`, `--section`, `--redact`, `--quiet`,
241
+ `--no-color`.
242
+ - `rvt-info --redact` and `rvt-history --redact` — PII propagation to the
243
+ other shipped CLIs.
244
+ - `elem_table` + `partitions` modules — Global/ElemTable + Partitions/NN
245
+ header parsers.
246
+ - `ifc` module — Layer 5 scaffold: `IfcModel`, `Exporter` trait,
247
+ `NullExporter`, full Revit-class → IFC-entity mapping plan.
248
+ - `writer::copy_file` — byte-preserving OLE round-trip (13 streams
249
+ identical, verified).
250
+ - 14 new reproducible probes under `examples/` covering every FACT in
251
+ the reconnaissance report.
252
+ - `tools/bench.sh` hyperfine benchmark harness + `docs/benchmarks.md`.
253
+ - First publicly-available RVT tag-drift table — `docs/data/tag-drift-2016-2026.csv`
254
+ (122 classes × 11 releases) + `tag-drift-heatmap.svg`.
255
+ - First publicly-documented Revit format-identifier GUID
256
+ (`3529342d-e51e-11d4-92d8-0000863f27ad`) — stable across every Revit
257
+ release 2016-2026.
258
+
259
+ ### Changed
260
+ - Library surface reorganised; `src/lib.rs` has a proper crate-level
261
+ doc with a quickstart example, moat-layer table, and module inventory.
262
+ - `FieldType::Primitive` now carries `{kind, size}` instead of
263
+ `{size_hint}`.
264
+ - `FieldType::Container` now carries a `kind: u8` field marking the
265
+ element base type (so `Container<u32>` is distinguishable from
266
+ `Container<f64>` / `Container<ref>`). Existing consumers that
267
+ destructure with `..` continue to work.
268
+ - `FieldType::decode` is now panic-safe on short inputs: 0/1/2/3-byte
269
+ slices produce either `Unknown` or a typed variant with an empty body
270
+ rather than a bounds-check panic.
271
+ - `scan_fields_until_next_class_bounded` respects `declared_field_count`
272
+ — fixes the over-reader that bled from HostObjAttr into Symbol's
273
+ fields.
274
+
275
+ ### Research findings (Phase 4c)
276
+
277
+ - **Q4**: The u16 "flag" in each tagged-class preamble is an
278
+ **ancestor-class reference**, not a bitmask. 9/9 non-zero values in
279
+ the 2024 sample resolve to named classes in the same schema.
280
+ - **Q5**: Decoded the field `type_encoding` byte sequence. 9 category
281
+ discriminators + sub-type variants.
282
+ - **Q5.1**: Extended to 84% coverage — wider primitive discriminators
283
+ (`0x01 bool`, `0x02 u16`, `0x05 u32`, `0x06 f32`, `0x07 f64`,
284
+ `0x08 string`, `0x09 GUID`, `0x0b u64`).
285
+ - **Q5.2**: Extended to **100.00%** coverage across the 11-version
286
+ corpus. Generalized `{scalar_base} 0x0010 ...` → `Vector<base>` and
287
+ `{scalar_base} 0x0050 ...` → `Container<base>` for every scalar base
288
+ (previously only `0x07 0x10` and `0x0e 0x50` were mapped). Added the
289
+ `0x0d` point/transform base (seen only in composite form), the
290
+ `0x08 0x60 ...` alternate string encoding, the `ElementIdRef { tag,
291
+ sub }` variant (for references that carry a specific referenced-class
292
+ tag — 80+ fields per release use this), the deprecated `0x03` i32-
293
+ alias (2016–2018 only, 5 fields), and robust handling of truncated
294
+ 2-byte `{kind}{modifier}` headers (schema-parse boundary artifacts).
295
+ - **Q6**: `Global/Latest` is **not** an index + heap. It's a flat
296
+ TLV stream.
297
+ - **Q6.1**: Instance data is **schema-directed** (tag-less, protobuf-
298
+ style). Decoding requires schema-first sequential walk from a known
299
+ entry point.
300
+ - **Q6.2**: Initial hypothesis — entry point located at offset `0x363`
301
+ in the 2024 sample (right after the document-upgrade-history
302
+ UTF-16LE block). Confidence 0.6. **Refuted by Q6.3.**
303
+ - **Q6.3 CORRECTION**: The Q6.2 entry-point hypothesis is refuted by
304
+ rigorous validation against the 11-version corpus. The bytes at the
305
+ post-history boundary are NOT ADocument's 13-field instance — they
306
+ are a multi-table directory / reference-pool with ~131 sequentially
307
+ numbered records per release (stable count across all 11 years,
308
+ unchanged from the 13 that would be expected if this were
309
+ ADocument). Body-size does not correlate with FieldType; body u16
310
+ values do not resolve to schema class tags (0/131 hit). ADocument's
311
+ actual location in `Global/Latest` (or another stream) is not yet
312
+ known — decoding the directory table format is the next open
313
+ research question (Q6.4+). Probes: `examples/adocument_walk.rs`,
314
+ `examples/post_directory.rs`, `examples/directory_class_lookup.rs`.
315
+ See `docs/rvt-moat-break-reconnaissance.md` §Q6.3 for full evidence.
316
+ - **Q7**: `Partitions/NN` trailer u32 fields are **not** per-chunk
317
+ offsets. Gzip-magic scan remains correct.
318
+
319
+ ## [0.1.0] — 2026-04-19
320
+
321
+ Initial public release.
322
+
323
+ - OLE2/MS-CFB container reader (via `cfb`) — Layer 1.
324
+ - Truncated-gzip decompression (via `flate2`) — Layer 2.
325
+ - Per-stream framing for `Formats/Latest`, `Global/Latest`,
326
+ `Global/ElemTable`, `Partitions/NN`, `Contents`, `PartitionTable`,
327
+ `RevitPreview4.0` — Layer 3.
328
+ - Schema table parser: class names + fields + tags + parent classes
329
+ + declared field counts + cross-release tag-drift map — Layer 4a.
330
+ - Phase D moat proof: class tags from `Formats/Latest` occur in
331
+ `Global/Latest` at ~340× uniform-random rate — Layer 4b.
332
+ - `FieldType` enum with 7 initial variants (Primitive, ElementId,
333
+ Pointer, Vector, Container, String, Guid). **84% field-type
334
+ classification** on a typical Revit 2024 sample family — Layer 4c.
335
+ - Stream-level modifying writer (`write_with_patches`) with
336
+ byte-preserving round-trips verified on all 13 streams — Layer 6.
337
+ - Seven shipped CLIs: `rvt-analyze`, `rvt-info`, `rvt-schema`,
338
+ `rvt-history`, `rvt-diff`, `rvt-corpus`, `rvt-dump`.
339
+ - Full PII-redaction (`--redact`) across every CLI.
340
+ - First publicly-documented Revit format-identifier GUID
341
+ (`3529342d-e51e-11d4-92d8-0000863f27ad`), stable across every
342
+ Revit release 2016–2026.
343
+ - First public RVT tag-drift table: 122 classes × 11 releases CSV
344
+ plus SVG heatmap.
rvt-0.1.2/CITATION.cff ADDED
@@ -0,0 +1,32 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use rvt-rs in academic or archival work, please cite it using this metadata."
3
+ title: "rvt-rs: Clean-room Rust reader for Autodesk Revit files"
4
+ type: software
5
+ version: 0.1.0
6
+ date-released: "2026-04-19"
7
+ license: Apache-2.0
8
+ repository-code: "https://github.com/DrunkOnJava/rvt-rs"
9
+ url: "https://github.com/DrunkOnJava/rvt-rs"
10
+ abstract: >-
11
+ rvt-rs is an Apache-2-licensed Rust library and CLI for reading
12
+ Autodesk Revit files (.rvt, .rfa, .rte, .rft) without any dependency
13
+ on Autodesk's SDK or a Revit installation. It is the first
14
+ open-source tool to enumerate the Formats/Latest class schema
15
+ inventory and has been verified against 11 Revit releases spanning
16
+ 2016 through 2026.
17
+ keywords:
18
+ - revit
19
+ - rvt
20
+ - rfa
21
+ - bim
22
+ - autodesk
23
+ - ifc
24
+ - file-format
25
+ - parser
26
+ - rust
27
+ - interoperability
28
+ - reverse-engineering
29
+ authors:
30
+ - family-names: Long
31
+ given-names: Griffin
32
+ alias: DrunkOnJava
@@ -0,0 +1,67 @@
1
+ # Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in
6
+ our community a harassment-free experience for everyone, regardless of
7
+ age, body size, visible or invisible disability, ethnicity, sex
8
+ characteristics, gender identity and expression, level of experience,
9
+ education, socio-economic status, nationality, personal appearance, race,
10
+ caste, color, religion, or sexual identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open,
13
+ welcoming, diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our
24
+ mistakes, and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the
26
+ overall community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or
31
+ advances of any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political
33
+ attacks
34
+ * Public or private harassment
35
+ * Publishing others' private information, such as a physical or email
36
+ address, without their explicit permission
37
+ * Other conduct which could reasonably be considered inappropriate in a
38
+ professional setting
39
+
40
+ ## Enforcement Responsibilities
41
+
42
+ The project maintainer is responsible for clarifying and enforcing our
43
+ standards of acceptable behavior and will take appropriate and fair
44
+ corrective action in response to any behavior that they deem
45
+ inappropriate, threatening, offensive, or harmful.
46
+
47
+ ## Scope
48
+
49
+ This Code of Conduct applies within all community spaces (GitHub issues,
50
+ pull requests, discussions, and any other project-affiliated channels),
51
+ and also applies when an individual is officially representing the
52
+ community in public spaces.
53
+
54
+ ## Enforcement
55
+
56
+ Instances of abusive, harassing, or otherwise unacceptable behavior may
57
+ be reported to the maintainer responsible for enforcement at
58
+ <151978260+DrunkOnJava@users.noreply.github.com>. All complaints will be reviewed and
59
+ investigated promptly and fairly.
60
+
61
+ ## Attribution
62
+
63
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
64
+ version 2.1, available at
65
+ <https://www.contributor-covenant.org/version/2/1/code_of_conduct.html>.
66
+
67
+ [homepage]: https://www.contributor-covenant.org
@@ -0,0 +1,137 @@
1
+ # Contributing to rvt-rs
2
+
3
+ Thanks for your interest. This project is small and evolving
4
+ quickly, so contribution guidelines are intentionally light — but
5
+ a few practices keep the repo healthy.
6
+
7
+ ## What's welcome
8
+
9
+ - **Bug reports** with a minimal reproducer (the smallest `.rfa`
10
+ or `.rvt` that triggers the issue). Security-sensitive reports
11
+ go through [`SECURITY.md`](SECURITY.md), not public issues.
12
+ - **Performance regressions** caught by the benchmark harness in
13
+ `tools/bench.sh` — open an issue with a before/after table.
14
+ - **New FACTs** about the file format. The reconnaissance report in
15
+ `docs/rvt-moat-break-reconnaissance.md` is the canonical place
16
+ for dated findings. Please mirror any new finding there AND as a
17
+ reproducible probe under `examples/`.
18
+ - **Documentation improvements.** The README and inline doc comments
19
+ are fair game.
20
+ - **Tests.** More coverage is always welcome, especially for
21
+ edge-case file layouts.
22
+
23
+ ## Where help is most wanted (as of v0.1.1)
24
+
25
+ The schema layer (Layer 4) is complete and regression-proofed: 100%
26
+ field-type classification across every Revit release 2016–2026,
27
+ enforced by CI. The instance layer (Layer 5) is wide open, with
28
+ three stacked sub-questions any of which is a genuine contribution:
29
+
30
+ 1. **§Q6.4 — decode the `Global/Latest` directory-table format.**
31
+ Right after the document-upgrade-history block in
32
+ `Global/Latest`, there is a contiguous sequential-ID TLV table
33
+ of ~131 records (stable across all 11 releases). Record bodies
34
+ are 2, 6, 12, 14, or 16 bytes; u16 body values are not schema
35
+ class tags (0/131 hit — see `examples/directory_class_lookup.rs`).
36
+ Strong candidates: ElementId references, indices into
37
+ `Global/ElemTable`, or a reference-pool. Cross-reference against
38
+ `Global/ElemTable`'s records and/or test on a non-sample-family
39
+ `.rvt` (any real project) to see whether the count is
40
+ per-document or format-structural.
41
+ 2. **§Q6.5 — locate ADocument's actual instance bytes.** Blocked on
42
+ Q6.4 — once we know what the directory indexes, we can follow
43
+ its entries to ADocument's real location (not the post-history
44
+ offset, as originally hypothesised and refuted in §Q6.3).
45
+ 3. **Layer 5a — schema-directed walker.** Blocked on Q6.5. The
46
+ skeleton has been scoped; implementation is bounded once Q6.5
47
+ produces the entry-point offset.
48
+
49
+ Each step has clear validation oracles: rvt-info already extracts
50
+ the document title + GUID via a second path, rvt-history extracts
51
+ the upgrade timeline via Phase D string scanning. Anything the
52
+ walker pulls must cross-check.
53
+
54
+ See `docs/rvt-moat-break-reconnaissance.md` §Q6 for the full state
55
+ of play, including the documented refutation of the original Q6.2
56
+ entry-point hypothesis (this is the first project section a
57
+ contributor should read before starting on Layer 5).
58
+
59
+ ## What needs discussion first
60
+
61
+ Open an issue (or a draft PR) before starting work on any of:
62
+
63
+ - **Layer 5 itself** — the questions above are open research; a
64
+ one-paragraph sketch of your approach in an issue saves everyone
65
+ time before you spend days on a probe.
66
+ - **IFC exporter emission** (`src/ifc/`). Mapping decisions have
67
+ to align with buildingSMART IFC schema conventions.
68
+ - **The modifying writer** (`src/writer::write_with_patches`). Any
69
+ change to Revit's truncated-gzip framing must be verified
70
+ against a round-trip test.
71
+ - **Layer 4c field-type decoder changes.** Coverage is at 100%
72
+ and CI-gated. If you think a pattern is misclassified, file an
73
+ issue with byte evidence from the corpus — do not silently
74
+ change the decoder.
75
+
76
+ ## Coding conventions
77
+
78
+ - Rust 2024 edition.
79
+ - `cargo fmt` before every commit.
80
+ - `cargo test --release` must pass. The CI in `.github/workflows/`
81
+ enforces this.
82
+ - **No `unsafe` in the library crate.** If you genuinely need it,
83
+ open an issue first to discuss.
84
+ - **No panics in parsing paths.** Malformed input must return an
85
+ `Error`, never `panic!`.
86
+ - **No PII in tests.** Use synthetic fixtures — `testuser`,
87
+ `111111`, `FY-20XX`, etc. The redaction tests in
88
+ `src/redact.rs` are the canonical examples.
89
+ - **Every probe under `examples/`** gets a module-level doc
90
+ comment explaining *what FACT it proves* and *how to verify*
91
+ the result against the 11-version corpus.
92
+
93
+ ## Commit messages
94
+
95
+ We use Conventional Commits:
96
+
97
+ - `feat(<scope>): ...` for new features
98
+ - `fix(<scope>): ...` for bug fixes
99
+ - `docs(<scope>): ...` for documentation
100
+ - `test(<scope>): ...` for test-only changes
101
+ - `refactor(<scope>): ...` for behavior-preserving internal changes
102
+ - `perf(<scope>): ...` for performance
103
+ - `chore(<scope>): ...` for infra / CI / build
104
+
105
+ Scopes that appear frequently: `formats`, `object_graph`,
106
+ `elem_table`, `partitions`, `writer`, `ifc`, `readme`, `cli`.
107
+
108
+ ## Reverse-engineering findings
109
+
110
+ When you discover something new about the file format:
111
+
112
+ 1. Write a short probe under `examples/<name>.rs` that reproduces
113
+ the finding from bytes. One self-contained file, runs against
114
+ the phi-ag/rvt sample corpus.
115
+ 2. Add a dated addendum to `docs/rvt-moat-break-reconnaissance.md`
116
+ with an evidence table and a confidence value.
117
+ 3. If the finding is a decoding rule, also add a unit test that
118
+ pins the byte pattern (see `FieldType::decode` tests in
119
+ `src/formats.rs` for the pattern).
120
+
121
+ This keeps every claim independently verifiable, which is the
122
+ whole point of open reverse-engineering work.
123
+
124
+ ## Legal note for contributors
125
+
126
+ rvt-rs is Apache-2.0 licensed. By submitting a contribution, you
127
+ agree that your work is licensable under Apache-2.0 and that you
128
+ have the right to grant that license.
129
+
130
+ **Please do not submit any code, comments, tests, or documentation
131
+ that contains information derived from Autodesk proprietary
132
+ sources** (NDA'd SDKs, decompiled binaries beyond what the public
133
+ `RevitAPI.dll` symbol export trivially exposes, leaked internal
134
+ documents, etc.). This project operates strictly from public
135
+ on-disk byte observations.
136
+
137
+ Questions: open an issue or email <151978260+DrunkOnJava@users.noreply.github.com>.