Skip to content
Omena
Concepts

Custom-property dependency resolution

The shipped dependency-graph and strongly connected component algorithm, its independent witness, and the remaining value-domain boundary.

Omena resolves custom properties by the dependency structure required by CSS Variables. The file name and several public Rust symbols retain earlier fixed-point wording for compatibility; the shipped algorithm no longer returns a bounded approximation.

Shipped algorithm

The implementation builds a directed graph over the canonical keys in CustomPropertyEnv. collect_custom_property_reference_indices visits every CascadeValue::Var, including references that appear in a fallback, so a fallback reference is a dependency edge rather than an escape from a cycle. Graph nodes are CanonicalCustomPropertyNameV0 values produced by PropertyNameV0, not independently normalized strings.

strongly_connected_components partitions that graph. A component is cyclic when it has more than one member or its only member has a self-edge. Every member of a cyclic component becomes CascadeValue::GuaranteedInvalid before any non-member is evaluated. A fallback therefore cannot rescue a declaration that belongs to the cycle. A non-member that later references an invalid cycle member may use its own fallback, which is the distinct outer-reference rule.

dependency_ordered_components schedules the acyclic remainder after its dependencies. Each binding is evaluated once against the memoized resolved environment by substitute_custom_properties_against_resolved_env. The schedule covers every component, and the implementation asserts both complete key coverage and the absence of remaining var() references. There is no non-converged-value return path. Both Kosaraju passes and the condensation-graph schedule are iterative, so a long alias chain does not consume the Rust call stack. Product value resolution omits the compatibility trace; summaries build each trace row from incremental counters instead of rescanning the environment.

Code correspondence

Semantic objectShipped symbolCorrespondence
Canonical graph keysCanonicalCustomPropertyNameV0 / PropertyNameV0Shared standard/custom property identity authority.
Input environmentCustomPropertyEnvFixed BTreeMap of canonical custom-property keys.
Value syntaxCascadeValueLiteral, composite, variable, CSS-wide states, indeterminate, guaranteed-invalid, and unset.
Dependency graphcustom_property_dependency_graphCollects references from primary values and fallbacks.
Reference collectorcollect_custom_property_reference_indicesMaps primary and fallback references to canonical graph indices.
SCC partitionstrongly_connected_componentsComputes the complete strongly connected component partition.
Component scheduledependency_ordered_componentsOrders distinct components after their dependencies.
Cycle predicatecomponent_is_cyclicDetects multi-member components and self-loops.
Resolved substitutionsubstitute_custom_properties_against_resolved_envApplies fallbacks only while evaluating an outer, non-cycle value.
Public value querysubstitute_custom_propertiesResolves the environment structurally, then substitutes the requested outer value.
Public environmentresolve_custom_property_env_least_fixed_pointCompatibility name returning the complete SCC-scheduled environment.
Public summarysummarize_custom_property_least_fixed_pointCompatibility shape exposing component-schedule observations and results.
Schedule observationCustomPropertyLeastFixedPointIterationV0One compatibility trace row per scheduled component.
Structural witnesscustom_property_bounded_fixed_point_computation_witnessCompatibility name describing the graph, SCC, and no-approximation invariants.
Completion bitreached_fixed_pointAlways true for a completed structural schedule; no false-valued approximation branch exists.

The trace fields retain their public compatibility names, but each row is a component-schedule observation rather than a Kleene iteration. Downstream RG-flow projections therefore keep monotoneKleeneCertificate false for non-empty structural summaries; a changing declaration count across component rows is not presented as convergence evidence. RG-flow recomputes the SCC count from summary.entries without reading the compatibility trace. That anchor is trace-independent, but it is not summary-independent and does not claim a second product oracle.

Frozen independent witness

The committed independent oracle's five-field oracle object defines an all-bottom status iteration. The complete evaluator kernel—from evaluate_from_all_bottom through evaluate_fixture_value and finalize_oracle_environment—and every expectedEvaluator projection are protected by separate SHA-256 checks. Every frozen case's authored bindings and cycleShape are covered by a third seal, so deleting a cycle edge or silently relabeling a structural control changes executable evidence.

The current eight-case corpus reports eight agreements and zero findings. Its named non-degenerate cycle-shape allowlist is exactly mutuallyRecursiveFallbackChain, cycleThroughFallback, and threeNodeFallbackCycleEnteredMidChain. The plain two-cycle retains the frozen baseline label mutualReferenceWithoutFallback, but that label is deliberately excluded from the novel-shape count. An outer-reference case separately proves that a non-member may use its fallback after its dependency is invalid.

The reordered-in-place mutation still weakens the independent simultaneous transfer and must fail. Product mutations that remove SCC classification or reintroduce a cycle-member fallback rescue must also fail.

Standard-property validation after substitution

A cascade winner for a standard property can contain var(), so its grammar cannot always be decided before custom-property substitution. The cascade crate exposes CascadeStandardValueValidatorV0 as a port; the product adapter SpecStandardPropertyValueValidatorV0 delegates to the spec-derived validate_standard_property_value_v0 authority.

compute_cascade_computed_value_with_standard_value_validator_v0 selects the winner, substitutes custom properties, and then validates the resulting standard-property text. A definite mismatch becomes invalid at computed-value time. A validator result that remains unknown becomes typed indeterminate. If the caller supplies no standard-property verdict, a literal or composite value also becomes typed indeterminate rather than resolved. CSS-wide keywords and a definite guaranteed-invalid substitution retain their independently known computed-value behavior.

The Salsa source-element path carries inline custom-property bindings into the same computation. Its regression case distinguishes --tone: red, which keeps color: var(--tone) resolved, from --tone: 12px, which is invalid for color. An always-valid validator or the former literal-only verdict filter makes that product-path test fail.

Value certainty separates two independent questions. Acceptance completeness asks whether the bounded matcher accepts every oracle-accepted keyword of a property; the pinned css-tree keyword-closure sweep answers it by following both type and property references (with a recursion guard and a maximum reference depth of 12), recording a tested-pair count for every property, and certifying only a nonempty property whose accepted single-keyword pairs all match. Identifier rejection is a separate authority that does not depend on that certification: a bare identifier is definitely invalid only when the recursively expanded property grammar has no open identifier production and the candidate is absent from that property's certificate-bound accepted keywords. A property with accepted matcher gaps therefore still rejects identifiers the oracle itself rejects, while every oracle-accepted keyword stays non-definite. At the pinned css-tree 3.2.1 input, the sweep tests 16,445 accepted pairs from 23,178 candidates across 704 properties. The executable compatibility regression is deliberately narrower: 11 property/value pairs containing 9 distinct keywords, not a claim that those samples cover each referenced grammar.

Builtin token profiles are generated from the same css-tree version. Their finite token samples witness representative token shapes; they are not an exhaustive enumeration of values within a token class. An absent type therefore defaults every token domain to open. Otherwise the result stays indeterminate. The reviewed <paint> override contains the complete SVG paint alternatives, and the matcher models the core calc()/min()/max()/clamp() and grid repeat()/minmax() paths.

That path resolves the custom-property environment at every ancestor boundary before applying child declarations. An inherited computed value therefore does not rebind to a child override of one of its former dependencies. A dynamic custom-property declaration is represented as indeterminate for that key, so it blocks a value that references it without blocking an unrelated static standard-property declaration.

Remaining rfcs#10 boundary

The committed register is rust/omena-custom-property-fixed-point-gap-register.json.

Gap idShipped stateClaims-under-test stateObservable consequenceUpgrade cost
conditional-value-domainCascadeValue has no conditional if() variant.Conditional custom-property values participate in the dependency and transfer domain.Branch-sensitive conditional dependencies cannot be represented or proved.Extend parsing and CascadeValue, define branch joins, and update every substitution and computed-value consumer.

The earlier cycle decomposition, clone-start approximation, and bound- exhaustion gaps are closed by the structural algorithm. The residual register does not claim a conditional-value theorem that the product value domain cannot express.

On this page