Skip to content

Parent Tree View security, performance, and data integrity

Parent Tree View derives structure only after applying the active Entity root View predicate and parent-field Field:Read predicate. The hierarchy cannot be used to discover a hidden parent, child, responsibility, or label.

For every record, the server uses the same captured Security Statements plan as ordinary Entity reads:

  • lifecycle scope and root View execute in PostgreSQL before pagination;
  • the Parent field must pass its Field:Read refinement;
  • role, user, responsibility, field, reference, and captured-clock facts use the same captured Security configuration;
  • archived scope additionally requires ReadArchive.

The structural query receives only the resulting readable parent id. It does not return which statement matched, the responsibility subject, or an activation reason. A conditionally readable Parent field cannot be exposed as a generic client-controlled sort/filter channel; this is a narrow server-owned edge projection.

The server first removes unreadable records. It then treats an unreadable, missing, or out-of-scope parent value as absent. The child becomes an effective root. Consequences:

  • hidden parents never appear as context;
  • a hidden child is absent from ChildCount;
  • counts include only eligible visible direct children;
  • ancestor closure traverses only visible, readable edges;
  • changing archive scope may change both roots and counts.

This behavior favors non-disclosure over reproducing the physical forest.

For a related Details/Drawer block, the server applies the configured relation as an eligibility scope before it computes effective roots or ancestor closure. An otherwise readable ancestor outside that relation therefore cannot be introduced as context. Child requests cannot supply or widen this filter; they echo the opaque host ContextRevision and the server rebuilds the scope.

Data stateRuntime behavior
Missing parentChild is an effective root; no parent id is disclosed.
Self-parentThe self edge is cut and the row reports SelfParentCut.
Finite cycleOne deterministic record (lowest id using invariant ordering) is cut to a root and reports CycleCut.
Deep valid chainLoaded lazily; visual indentation is capped after eight levels while accessible depth remains exact.

Only the cut row carries a safe issue enum. No warning includes the other record id. V1 has no separate administrator hierarchy-diagnostics endpoint.

V1 intentionally does not add automatic cycle prevention to every Reference write. Teams that require stricter semantics should add business validation for allowed parents, maximum depth, or cycle prevention. Concurrent reparenting must use normal record RowVersion checks and transactions supplied by the supported record services.

Every Tree parent field must have a valid, ready BTREE index whose first key is the physical parent column. Runtime rechecks physical shape. It rejects:

  • an index with another leading key;
  • an expression index;
  • a partial index;
  • an invalid or not-ready index.

Tree configuration never creates or drops database indexes. Dynamic fields normally receive their managed automatic index. Package-owned schema must declare HasIndex and ship an EF migration.

If index drift occurs after configuration, authoring reports MissingBtreeIndex and runtime returns a typed unavailable error without executing the hierarchy query. Restore a qualifying index through Entity Explorer schema management or the owning package migration, then reload.

One request resolves one root or child sibling page and exact child counts for that page. It does not run one count query per row. Server sorting and the parent-leading index keep wide child lookup bounded; lazy loading avoids materializing the complete tree in the browser.

For a related-table child request without a user filter, the server constrains the requested parent and direct children to the already resolved eligibility scope. It does not normalize or materialize the complete related scope before returning that child page.

The V1 release qualification envelope is:

  • 100,000 physical fixture rows, approximately 95,000 active candidates before actor-specific access filtering;
  • a sibling set up to 25,000 records;
  • hierarchy depth up to 100;
  • PageSize <= 250.

Larger shapes are not performance-qualified by this release. Prefer a composite (Parent, common-sort-field) index for exceptionally wide branches, avoid recursive expand-all behavior, and model subtree bulk work as an explicit server operation rather than repeated UI expansion.

A descendant-only filter needs visible ancestor closure. Selective filters are usually cheaper than non-selective filters, but both must preserve security and exact sibling counts. Changing filter/search invalidates every loaded branch so results from the previous query cannot be inserted.

  1. Read configuration options and confirm the parent option is Ready.
  2. Bootstrap with active scope and record root count and surface revision.
  3. Load one wide child page and confirm HasNextPage.
  4. Search for a descendant and confirm only visible ancestors have IsAncestorContext = true.
  5. Test as an ordinary reader; compare neither ids nor counts with an admin response in a user-visible error.
  6. Archive a parent in a test environment and verify the readable child becomes an effective root.
  7. Repair any SelfParentCut or CycleCut through ordinary governed record updates.

Do not use root count as a global result count, expose raw parent ids in errors, cache child pages across a surface/query revision, bypass the index check, or treat runtime cycle normalization as domain validation.