From 6fe8845b7d17719dc048ad4f7b5e72832523ed5d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:25:37 +0000 Subject: [PATCH 1/2] CI: gate the OFF half of the activation contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #257 shipped the plug-and-play activation but tested it in only ONE direction, which I noticed after it merged. `cargo test --workspace` cannot exercise the off path: `ogar-ro` dev-deps `ogar-blockly`, so feature unification turns ogar-vocab's `blocks` feature ON for the entire workspace run. Every test asserting "activated" therefore ran, and nothing ever asserted "not activated" — the exact vacuous shape of a guard nobody watched stay silent. The one proof I had was a throwaway crate outside the workspace, which CI never sees. Two tests under `cfg(all(test, not(feature = "blocks")))`, so they VANISH when any activating feature is on rather than passing hollowly (measured: 2 tests off, 0 tests on). They assert a default build activates nothing, that 0x1717 answers UnknownClassid, that the domain still routes on the reserved byte alone, and that no 0x17XX row ever reached `class_ids::ALL` — the surface mirrored into lance-graph under the compile-time fuse, caught on THIS side first. Plus the CI step that can reach them: `cargo test -p ogar-vocab`, crate-scoped so `ogar-blockly` is not in the graph. The gate is verified to fail when it should: seeding one row into the `not(blocks)` arm of `activated_concepts` turned the first test red, then restored. --- .github/workflows/ci.yml | 14 +++++ crates/ogar-vocab/src/capability_registry.rs | 58 ++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a16656..3720e1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,20 @@ jobs: run: cargo check --workspace --all-targets - name: cargo test --workspace run: cargo test --workspace + # The OFF half of the plug-and-play activation contract. + # + # `--workspace` CANNOT test it: `ogar-ro` dev-deps `ogar-blockly`, so + # feature unification turns ogar-vocab's `blocks` feature ON for the + # whole workspace run. Crate-scoped, `ogar-blockly` is not in the graph, + # the feature is off, and the `cfg(not(feature = "blocks"))` tests in + # `capability_registry` become reachable — the ones asserting a default + # build activates NOTHING and a frontend classid does not resolve. + # + # Without this step the design is only ever tested in the triggered + # direction, which is the vacuous shape of a guard nobody watched stay + # silent. + - name: cargo test -p ogar-vocab (default features — the OFF half) + run: cargo test -p ogar-vocab # Exercise the feature-gated surrealql AST walk (lifts DDL -> # Class via the surrealdb-parser fork). Crate-scoped because the # parser dep is heavy and not needed by other workspace members; diff --git a/crates/ogar-vocab/src/capability_registry.rs b/crates/ogar-vocab/src/capability_registry.rs index 33aee3e..a6efa54 100644 --- a/crates/ogar-vocab/src/capability_registry.rs +++ b/crates/ogar-vocab/src/capability_registry.rs @@ -389,6 +389,64 @@ fn activated_concept_id(concept: &str) -> Option { .map(|&(_, id)| id) } +/// The OFF half of the activation contract — compiled ONLY when no feature +/// activated anything. +/// +/// The positive half (`blocks_actions::tests`) can only run with the feature +/// ON, and a workspace build unifies it ON for every crate (`ogar-ro` +/// dev-deps `ogar-blockly`). So `cargo test --workspace` **cannot** exercise +/// the claim that a default build carries zero activated rows — the property +/// the whole design rests on. This module is that gate: it is +/// `cfg(not(...))`, so it vanishes the moment any activating feature is on, +/// and CI runs `cargo test -p ogar-vocab` (no `ogar-blockly` in the graph) to +/// reach it. +/// +/// Without it, "the codebook is triggered by plug-and-play" would be tested +/// only in the triggered direction — the vacuous shape of a guard nobody +/// watched stay silent. +/// +/// Verified to FAIL when it should: seeding one row into the `not(blocks)` +/// arm of [`activated_concepts`] turns the first test red. +#[cfg(all(test, not(feature = "blocks")))] +mod default_build_carries_no_activated_rows { + use super::{HotplugDrift, activated_concepts, resolve_hotplug}; + + #[test] + fn nothing_is_activated_and_a_frontend_classid_does_not_resolve() { + assert!( + activated_concepts().is_empty(), + "a default build must activate nothing" + ); + // 0x1717 is the Blocks palette. No block editor is in THIS build, so + // the honest answer is that the vocabulary is absent. + assert!(matches!( + resolve_hotplug("blockly-abi", &[0x1717], &[]), + Err(HotplugDrift::UnknownClassid(0x1717)) + )); + // …and the domain still routes on the reserved byte alone, which is + // what lets a consumer branch on 0x17XX with no concept minted. + assert_eq!( + crate::canonical_concept_domain(0x1717), + crate::ConceptDomain::Blocks + ); + } + + #[test] + fn the_canon_is_untouched_by_the_activation_seam() { + // The count mirrored into lance-graph under the compile-time fuse. If + // an activated row ever leaks into `class_ids::ALL`, this moves and + // the lance-graph mirror breaks — catch it on THIS side first. + assert_eq!(crate::class_ids::ALL.len(), 90); + for (_, id) in crate::class_ids::ALL { + assert_ne!( + *id >> 8, + 0x17, + "a 0x17XX row reached the globally-mirrored codebook" + ); + } + } +} + fn activated_concepts() -> &'static [(&'static str, u16)] { #[cfg(feature = "blocks")] { From c530436b5c63ac975926a2c54bce87887697ce57 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:33:54 +0000 Subject: [PATCH 2/2] Close codex P2: the OFF gate must not be able to switch itself off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex is right, and it names the hole I had flagged in a check-in note without actually closing: `default_build_carries_no_activated_rows` is `cfg(not(feature = "blocks"))`, so putting `blocks` into `[features] default` compiles it out, `cargo test -p ogar-vocab` exits 0, and the regression the gate exists to catch becomes invisible. A gate that can disappear is not a gate. Two defenses, because the obvious one is necessary but not sufficient: 1. CI now uses `--no-default-features`, forcing the off build regardless of what `default` contains — and asserts the named tests actually RAN by grepping the output, since a filtered-out suite exits 0 too. 2. `the_off_gate_cannot_be_switched_off` — ALWAYS compiled, in every feature configuration. It reads the crate's own Cargo.toml via `include_str!` and fails if any activating feature reached the default set. Defense 1 only forces the build a human remembered to write that way; this one cannot be forgotten or cfg'd away, and it is what makes the property hold under the plain command too. Falsified by simulating the exact regression (`default = ["blocks"]`): - plain `cargo test -p ogar-vocab` -> exit 101, manifest guard fires with the diagnostic naming the cause. Note this ALSO closes the old step, not just the new one. - `--no-default-features` -> both OFF tests still run (the feature is forced off), and the manifest guard still fails. Then restored; `default = []`. OFF 156 pass / ON 158 pass / workspace 0 failures / clippy -D warnings clean / fmt scoped with -p. --- .github/workflows/ci.yml | 21 +++++++++- crates/ogar-vocab/src/capability_registry.rs | 43 ++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3720e1b..8e3d10b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,8 +42,25 @@ jobs: # Without this step the design is only ever tested in the triggered # direction, which is the vacuous shape of a guard nobody watched stay # silent. - - name: cargo test -p ogar-vocab (default features — the OFF half) - run: cargo test -p ogar-vocab + # `--no-default-features` FORCES the off build, so this job still + # reaches the gate even if `blocks` were added to `[features] default` + # (codex P2 on #259: with a plain `cargo test -p ogar-vocab` the module + # would compile out, cargo would exit 0, and the regression the gate + # exists to catch would be invisible). + # + # Exit code alone is NOT evidence here — a filtered-out test suite exits + # 0 too. So assert the named tests actually RAN. `the_off_gate_cannot_be + # _switched_off` is the always-compiled companion that fails if any + # activating feature reaches the default set. + - name: cargo test -p ogar-vocab (OFF half — forced, and proven to run) + run: | + set -euo pipefail + out=$(cargo test -p ogar-vocab --no-default-features 2>&1 | tee /dev/stderr) + for t in default_build_carries_no_activated_rows \ + the_off_gate_cannot_be_switched_off; do + echo "$out" | grep -q "$t" \ + || { echo "::error::OFF-half gate '$t' did not run — it compiled out"; exit 1; } + done # Exercise the feature-gated surrealql AST walk (lifts DDL -> # Class via the surrealdb-parser fork). Crate-scoped because the # parser dep is heavy and not needed by other workspace members; diff --git a/crates/ogar-vocab/src/capability_registry.rs b/crates/ogar-vocab/src/capability_registry.rs index a6efa54..bd15bb2 100644 --- a/crates/ogar-vocab/src/capability_registry.rs +++ b/crates/ogar-vocab/src/capability_registry.rs @@ -389,6 +389,49 @@ fn activated_concept_id(concept: &str) -> Option { .map(|&(_, id)| id) } +/// Guard on the guard — **always compiled**, in every feature configuration. +/// +/// [`default_build_carries_no_activated_rows`] is `cfg(not(feature = +/// "blocks"))`, which makes it a gate that can DISAPPEAR: put `blocks` into +/// `[features] default` and the module compiles out, the job still exits 0, +/// and the regression the gate exists to catch becomes invisible (codex P2 on +/// #259 — correct, and the hole I had named in a check-in note without +/// actually closing). +/// +/// CI defends this with `--no-default-features`. That is necessary but not +/// sufficient: it only forces the build a human remembered to write that way. +/// This test is the part that cannot be forgotten or cfg'd away — it reads +/// the crate's OWN manifest at compile time and fails if any activating +/// feature has been added to the default set, in EVERY configuration, +/// including the one where the OFF module is absent. +#[cfg(test)] +mod the_off_gate_cannot_be_switched_off { + /// Every feature that activates codebook rows. A new activating feature + /// is added here in the same PR that introduces it. + const ACTIVATING: &[&str] = &["blocks"]; + + #[test] + fn no_activating_feature_is_in_the_default_set() { + // Compile-time read of this crate's own Cargo.toml — no runtime I/O, + // and no way for a feature flag to hide it. + let manifest = include_str!("../Cargo.toml"); + let default_line = manifest + .lines() + .map(str::trim) + .find(|l| l.starts_with("default")) + .expect("ogar-vocab must declare a `default` feature list"); + for feature in ACTIVATING { + assert!( + !default_line.contains(feature), + "`{feature}` is in the DEFAULT feature set ({default_line}). \ + Every build would then carry that codebook, and the OFF-half \ + gate would silently compile out. Activation must stay opt-in, \ + turned on by the consumer that owns it." + ); + } + } +} + /// The OFF half of the activation contract — compiled ONLY when no feature /// activated anything. ///