10 KiB
Empty inheritance-chain scaling diagnosis — 2026-09-11
Codex-ytr83r /root/integration_audit. Bounded read-only source diagnosis of root's already executed ignored R6 probe. No compiler, Cargo, repeated benchmark, production edit, Git mutation or native input by this reviewer.
Finding: there is avoidable repeated work in the current validation path, including full-model scans even when every record has zero fields. This is a confirmed source scaling limitation, not a newly demonstrated semantic failure or a measurement of optimized GUI latency. The observed debug wall times cannot attribute how much cost belongs to the algorithm, profile or contended host.
Read issue #1 through owner comment 229 and the updated branch AGENTS.md blob
4b8465c8993107062ad2974c15a958d449530e92, including harness maintenance rules.
Ownership coordination is comment 230.
Codex-956atf retains core numeric/metamodel work; comment 231 subsequently assigns
their schema-8 core work to an isolated worktree. No inheritance implementation
lease is claimed here. This focused report supplements the historical evidence
below rather than replacing it or reopening its completed semantic work.
What was measured
Root invoked target/debug/deps/r6_data-01b62bfdfd2168ff.exe --ignored --exact inheritance_chain_validation_scaling_probe --nocapture. Executable SHA-256:
4e79ed783ad4e147f5e1b4cffa5a41d9d383521cd336e99fc74407b067275ea4.
Original evidence: .local/ytr83r-inheritance-probe/probe.log and status.json.
| Records | Root-observed validation wall time | Ratio to preceding size |
|---|---|---|
| 250 | 6669.691 ms | — |
| 500 | 19166.328 ms | 2.87 |
| 1000 | 38062.849 ms | 1.99 |
Test harness: one passed, zero failed, 71.21 seconds. Outer runner: exit 0, timeout false, 73.562 seconds. The sum of three printed intervals is 63.899 seconds; the residual includes fixture construction and other harness work, which this probe does not time separately. Do not call the entire 71.21 seconds validation time or infer its residual cause.
r6_data.rs:1325 constructs a fresh Project with N top-level OA Record types,
one supertype chain and no fields/relations/diagrams. make_record is at line 15.
Construction, UUID creation and alias generation happen before each timer.
The timed valid helper at line 56 runs full validation::validate, filters
errors and asserts none; it does not assert an empty warning set. No source
parsing, serialization, file I/O, commands, layout or drawing is timed. Each
size runs once; UUIDs and thus map/traversal ordering are freshly generated.
The debug test ran while desktop dependencies compiled. Root reports an unusually slow host (first core compilation 37m12s, approximately 3 GiB free, normal-priority rustc). These observations make environmental contribution plausible; no CPU-time, allocation, paging or scheduler measurement isolates it. The two observed ratios neither prove linearity nor fit a reliable exponent.
Confirmed source cost
validation.rs:182 constructs one validation snapshot but no shared data index.
Every record reaches data::validate_element (validation.rs:411), then all
records reach validate_record_cycles (validation.rs:609, data.rs:1147).
Let depth d include the record itself, with d = 1..N in this fixture.
| Exact work in this fixture | Source | Count per whole validation |
|---|---|---|
effective_fields(base) for shadow checks |
data.rs:688 |
N−1 calls |
effective_fields(self) for empty-record checks |
data.rs:827 |
N calls |
effective_fields(self) for mandatory Value graph |
data.rs:1153 |
N calls |
| All-element filter visits inside those calls | data.rs:58 |
N(3N−1) |
| Local-child filter visits for derived records | data.rs:693, model.rs:1828 |
N(N−1) |
| Named-field reference scan for empty records | data.rs:828 |
N² |
| Ancestry-node visits across those calls plus separate cycle checks | data.rs:42, data.rs:678 |
2N² |
| Whole identity-vector materializations for supertype existence | validation.rs:329, model.rs:1746 |
N−1 vectors of N+1 entries |
The ancestry count is 2 sum(d−1) + 2 sum(d) = 2N². Each ancestry walk allocates
a visited BTreeSet and a result Vec, performs Project BTreeMap lookups, and each
effective-fields call builds another ranks BTreeMap. All field vectors are
empty, so output size and sorting cannot explain this fixture's repeated work.
The three full-element data scans contribute 5N²−2N candidate visits before
the separate identity-vector materializations/searches are counted.
Project::contains calls identities() afresh, then performs a linear search.
Each of the N−1 supertype references triggers it even though validation already
has the complete identity BTreeSet from validation.rs:198. This is another
quadratic floor; optimizing effective fields alone cannot remove it.
For this bounded-string, field-free fixture, the inspected path has at least
quadratic work and an O(N² log N) tree-operation upper bound. There is no current
ancestor × full-model nested scan within one effective_fields invocation;
its existing comment means one model scan per call, not per validation snapshot.
The mandatory-value sink-removal algorithm itself has no edges here; its
repeated effective-field graph construction is the relevant cost. Value path
depth/expansion limits are not exercised and do not bound these scans.
This predates B4: the data.rs Git blob is identical in original integration
0b58107 and inspected HEAD 023e3a8f097158ac84ca47a58cfa917c503804b0:
df061a8cfa0d90a4f94679dc888cdc00c11156a3.
Historical R6 verification line 32 records older
single-run optimized Windows 21.070/89.088/391.590 ms and Linux
22.381/111.662/408.129 ms at these sizes. Those older host/source measurements
are consistent with superlinear growth, but are neither rerun here nor a current
performance guarantee. The data contract line 148
already calls for shared computation once per validation snapshot; current
helpers do not fully implement that performance design.
Bounded alternatives for the core owner
- Reuse validation's existing exact identity set for its reference membership
checks. Preserve the global namespace (including diagram nodes/artifacts/
traces), duplicate-ID diagnostics and malformed map-key behavior. This is
smaller than changing public
Project::containsglobally, but only removes the identity-materialization contribution. - Build a borrowed data index per immutable validation snapshot: local fields by owner, referenced named record IDs, immediate supertypes, and cycle state. Compute/share effective field sequences base-first, preserving original IDs/owners and UUID order within each owner; derive existence and mandatory Value-edge summaries without rescanning all elements. Use iterative cycle processing or bounded explicit stacks. Empty chains should require roughly linear graph work plus map costs; actual field-rich output remains output-sensitive. N types each adding a field inherently yield Θ(N²) materialized inherited field entries if all such lists are requested.
- Merely memoizing today's complete helper results cuts repeated calls but still walks each full ancestry and scans the model once per owner. Indexing only local children also leaves ancestry and identity costs. These are possible staged constant-factor improvements, not a near-linear claim.
Do not use a persistent UUID-only cache across mutable Projects, silently cap valid inheritance depth, remove cycle checks, or change incomplete-model diagnostics for speed. For invalid graphs, preserve current diagnostic identity, severity and ordering: descendants leading into a cycle are distinct from cycle members; missing/wrong-family/cross-layer bases still need their existing errors. A validated acyclic fast path with the existing invalid-graph fallback is one conservative option. Any production change remains with the core owner.
Minimal falsifiers and next measurement
- A later instrumented validation of a small empty chain should reproduce the listed counts. Disagreement falsifies this accounting before optimization. Flat empty records isolate the full-model scans while removing deep ancestry; a star and a chain at equal N separate depth-sensitive work. Fixed UUIDs and frozen fixtures remove ordering noise. Do not use timing assertions as unit tests.
- Compare the complete ordered Issue output and unchanged Project for valid chains, self/two-node cycles plus a tail into a cycle, missing/wrong-family/ cross-layer bases, inherited name shadowing and same-snapshot repeated calls. Revalidate after base edit, remove, reidentify and Undo using a fresh snapshot to expose stale caches. A single inherited field checks identity/order; a field-rich chain distinguishes unavoidable output from empty-chain overhead. Required Value cycles, optional/Reference recursion and inherited Call/Reply paths must retain existing semantics if their shared helpers are changed.
- After compiler work ends, root/core owner should build one optimized probe from a frozen commit with recorded Rust version, target, profile, flags and binary hash. Run it outside Cargo with no concurrent builds: one warm-up and three measured samples per fixed 250/500/1000 fixture, reporting every sample and median. Record process CPU time alongside wall time, working set and host conditions; use a bounded timeout (for example 60 seconds per case) and stop rather than treating timeout as a result. Compare current source before any change with the same-machine candidate; do not compare debug on this host directly with historical optimized machines.
- Finally measure the actual optimized native workflow on a representative model: open, one inheritance edit/validation, responsive redraw, Save and Undo/Redo. Separate core validation from parsing/layout/persistence and count validation invocations. Only that gate can establish user-visible latency; the current ignored probe alone cannot.
No new latency budget or required release threshold is invented here. Root and the core owner should choose the next change from controlled optimized evidence and the actual authoring workload, preserving B4's independent acceptance gates.