vike
Developer

Structure gates

Three gates over the shape of the workspace — dependency direction, compile-time paths, and re-spelled primitives — and how to read each one's failure.

Three gates fail on the shape of a change rather than on its behaviour. The rule the first one enforces is explained on Architecture; this page is about reading the failure when one of them goes red, because in all three cases the obvious repair is the wrong one.

layer_gate.rs — dependency direction

Every workspace member declares its rank in its own manifest, and crates/vike-ops/tests/layer_gate.rs's every_normal_vike_dependency_goes_strictly_down asserts that for every normal (non-dev) vike-* edge c -> d, layer(d) < layer(c). Text-only over the manifests, no cargo invocation.

Reading the failure. The edge is the defect; the number rarely is. The ranks in TIERS were established by reading the real graph — all 275 normal vike-* edges in the tree the gate was written against — and every one of them satisfied the rule as merged, so the tree had no layering violation to fix when the gate landed. Raising the rank of the crate that failed will make the gate pass and will also pull whatever that dependency drags in.

That is not abstract. One normal edge from the headless trading daemon to the GUI's shared crate dragged the entire egui widget stack — ten crates — into the daemon, for three pure modules. The cost was never CI minutes: it was daemon build time, binary size, and the cargo deny --all-features supply-chain audit surface on the box that signs real orders. It was found by somebody noticing build times.

A rank is a total order; the architecture is a partial one

Where two families are genuinely incomparable the numbers still put one above the other, and the gate then permits the wrong-direction edge in whichever direction was chosen. Two of those choices are deliberate, made toward the mistake that has actually happened: the whole UI stack ranks above everything headless, which is what makes the motivating edge fail; and venue bridges rank below the simulator, so a bridge reaching for the backtester is red while the reverse edge — the far less dangerous one — is not.

Dev-dependencies are exempt, and that is not a loophole: a dev-dep is a test edge, and this workspace dev-depends upward on purpose. dev_dependencies_genuinely_point_upward asserts the exemption is still needed, so nobody can fold dev edges into the rule and have it pass for the wrong reason. [build-dependencies] are not exempt, and optional versus non-optional is not distinguished — an optional normal dep is still an edge somebody can turn on.

compile_time_path_gate.rs — a running binary may not use its build tree

env! over the manifest directory expands at compile time to the directory of the crate being built, so the value names the tree the binary was built in, never the project it is later run from. On the box that built it those two coincide, which is exactly why the defect is invisible everywhere except in production.

The tree convicted itself before the gate existed: a venue bridge built its sidecar-jar and JVM fallbacks that way, so a binary built elsewhere and deployed into a project folder found neither and degraded to paper with nothing in the log but "bridge jar not found". Both reads were already pinned by the settings registry — and the deployment consequence was gated by nothing at all. Both sites were retired in the batch that added this gate; it exists to stop the class growing back.

Reading the failure. Every use in non-test src/ of a workspace member is keyed on (file, enclosing fn) and carries a Disposition — a closed three-variant classification, not a free-text reason column, because these are not exemptions: several are debt somebody should still retire, and a reason column is an invitation to write a plausible sentence and join the list. The class that claims mitigation is machine-checked: every_ladder_rung_row_is_corroborated_by_its_file requires such a file to actually reach the runtime store-root resolver, and requires every other row's file not to. So the repair is either to route the path through the runtime resolver or to classify the site honestly — never to add a row that says something plausible.

Three exclusions fall out of deriving the roster from [workspace] members rather than maintaining a list: vendored third-party code, the excluded protobuf regeneration recipe (whose own compile-time path is correct — the build tree is its subject), and tests//benches/, where resolving a committed fixture from the manifest directory is right and a gate arguing with roughly 50 such sites would teach people to write exceptions.

duplicate_shape_gate.rs — a hoisted primitive may not be re-spelled

The motivating commit added four fresh copies of the closing-side ternary to the backtest engine six days after that helper was hoisted into the shared scalar module. CI was green.

The gate walks the real crates/ tree, masks comments and string literals, and looks for the banned expression shapes textually. Each shape names the one helper it should call: the closing-side ternary, and a hand-rolled quantity·price·multiplier product — which resolves to one of three notional helpers that are not interchangeable, because picking the wrong one silently changes risk arithmetic.

Reading the failure. ALLOWLIST carries an exact count per (file, shape), and it fails in both directions: too many sites is a new re-spelling, too few is a stale exemption. So converting an allowlisted site forces its entry to be revisited rather than quietly outliving its reason. The gate is textual on purpose and must not become an AST pass — an approximate matcher plus an explicit, reasoned allowlist beats a precise matcher nobody maintains. Its limits are declared: four-or-more factor products are margin formulas with their own helpers and are out of scope, and factors are classified by identifier word, so a price bound as a bare p carries no price word and escapes.

On this page