Skip to content

build: split the Cargo gates into a contributor set and a product set - #5468

Merged
senamakel merged 1 commit into
tinyhumansai:mainfrom
senamakel:build-time-phase3
Aug 10, 2026
Merged

senamakel merged 1 commit into
tinyhumansai:mainfrom
senamakel:build-time-phase3

Conversation

@senamakel

@senamakel senamakel commented Aug 9, 2026

Copy link
Copy Markdown
Member

Rebased onto current main. #5467 — the main repair this was originally stacked on — is closed: #5469 landed the same fixes upstream. This now applies directly to main with no dependency.

main gained a runtime-node gate while this was open. It is in the product set here and out of the contributor set, because it carries a native build (lzma-sys).

Why

[features] default was the product set, so every contributor paid for the whole product on every edit — web3's ethers/secp256k1 cohort, documents' zstd/bzip2 native builds, the cpal/hound/arboard/enigo/rdev stack behind voice+inference, contacts' macOS objc2 cohort, crash-reporting's sentry tree, tui's ratatui. A bare cargo check, and therefore rust-analyzer on every keystroke, resolved 540 packages and ran 7 native C/C++ builds.

What

default is now the contributor set — 9 gates that cost almost nothing to compile, so cargo check still typechecks nearly the whole tree.

Contributor ([features] default) Product (scripts/ci/product-features.txt)
Gates 9 16
Packages 353 540
Native builds 3 7

Kernel floor is untouched at 305/282/4 — this axis does not intersect the flows profile.

This does not change what ships. app/src-tauri has declared openhuman_core with default-features = false since #1061 and never inherited default — which is precisely why a forwarding guard had to exist at all.

The guard rewrite is in this PR on purpose

check-feature-forwarding.mjs worked by diffing the shell's forwarding list against [features] default. That is a subset check, sound only while default was the product set. Shrinking default makes it weaker with every gate removed, and vacuous if default ever empties — silently re-arming #4901, where voice shipped missing for three minor versions across 56 users and ~93k Sentry events. Landing the flip without the rewrite would have been the same bug with a longer fuse.

The product set is explicit now, and the guard asserts three things:

  1. the shell forwards exactly that file — set equality, both directions. A dropped gate fails on missing; a gate the shell grew that the product never claimed fails on unexpected. Equality cannot pass vacuously.
  2. every name in the file is a gate the core actually declares — catches a typo, or a gate renamed out from under it.
  3. every default gate is forwarded or allow-listed — the original check, retained because it still catches a contributor-set gate nobody shipped.

Two parsers read that file: the guard, and product-features.sh, which the CI lanes use to build --features. A test asserts they agree — if they drifted, CI would compile a different set than the guard checks. 27 self-tests pass.

The fallout, and how it is handled

A lane that relied on default features silently stops covering the product. Every lane that builds or tests the product now passes --features "$(bash scripts/ci/product-features.sh)": core clippy, the unit lane, the changed-files coverage lane, and scripts/test-rust-with-mock.sh.

Clippy runs twice, once per set. A lint that only fires with the gates off — an import left unused once a domain is compiled out — is invisible to the product lane and would be red on every contributor's machine while CI stayed green. The contributor graph is 353 packages against the product's 540, so it is cheap.

Four tests/*.rs targets name symbols that only exist behind voice, web3, inference or crash-reporting, so they declare required-features. Without it a bare cargo test fails to compile, in files the contributor did not touch. With it cargo skips them — the same silent-skip trap --bins without bin-tools already had, which is why the lane comments spell it out: json_rpc_e2e alone is >12k lines of RPC contract coverage.

tui lands in neither set (default-OFF and deliberately not forwarded), so nothing in CI would compile it at all. The feature-gate-smoke lane now checks it explicitly; a future gate in that position needs the same treatment.

Reviewer notes

Verification

cargo check --lib --tests                                        # contributor set
cargo check --all-targets --features "$(scripts/ci/product-features.sh)"
cargo check --no-default-features --features tui
node --test scripts/__tests__/feature-forwarding.test.mjs        # 27 passed
node scripts/ci/check-feature-forwarding.mjs
bash scripts/check-kernel-floor.sh
cargo fmt --all -- --check

Summary by CodeRabbit

  • Enhancements

    • Standardized desktop product feature configuration across builds, tests, coverage, and CI checks.
    • Added validation to detect missing, unexpected, or unsupported product feature gates.
    • Expanded feature-gate testing, including explicit checks for optional interfaces and integrations.
  • Documentation

    • Clarified the distinction between contributor defaults and shipped product features.
    • Updated feature guidance and the minimal library recipe to reflect current defaults.
  • Tests

    • Added comprehensive coverage for feature parsing, forwarding, manifest validation, and configuration consistency.

@senamakel
senamakel requested a review from a team August 9, 2026 08:41
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR separates contributor and product Rust feature sets. It adds product-feature parsing and forwarding validation, gates feature-dependent integration tests, and applies product features across CI, coverage, and mock test workflows.

Changes

Product Feature Forwarding

Layer / File(s) Summary
Feature set contracts
scripts/ci/product-features.txt, Cargo.toml, AGENTS.md, docs/library-minimal-recipe.md
The repository now defines separate contributor and product feature sets. Cargo defaults use contributor features, and feature-dependent integration tests declare required gates.
Forwarding parsers and validation
scripts/lib/feature-forwarding.mjs, scripts/ci/check-feature-forwarding.mjs, scripts/__tests__/feature-forwarding.test.mjs
The validation tooling parses feature names, checks exact product forwarding, rejects unknown or inherited gates, and tests real manifest agreement.
CI and coverage feature selection
scripts/ci/product-features.sh, .github/workflows/ci-lite.yml, .github/workflows/test-reusable.yml, scripts/ci/rust-coverage-changed.sh, scripts/test-rust-with-mock.sh
CI and test commands derive and apply product features. Clippy also checks contributor defaults, and the tui gate receives a separate disabled-feature smoke check.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProductManifest
  participant ProductFeaturesShell
  participant CIWorkflows
  participant Cargo
  ProductManifest->>ProductFeaturesShell: define product feature gates
  ProductFeaturesShell->>CIWorkflows: provide comma-separated feature list
  CIWorkflows->>Cargo: run tests, clippy, and coverage with product features
Loading

Possibly related PRs

Suggested labels: rust-core

Suggested reviewers: m3ga-mind

Poem

A rabbit checks each feature gate,
Product paths now validate.
Clippy hops through every set,
Tests run with gates correctly met.
Tui waits in a smoke-test pen—
Carrots for CI, then back again! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: separating Cargo features into contributor and product sets.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: 2 lane(s) blocking, worst finding is high.

Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.

             $0.9223 · 424,334 in / 14,685 out · 37,504 cached (9%)  · minimax/minimax-m3, moonshotai/kimi-k3
critique:    $0.1312 · 43,882 in  / 107 out    · 768 cached (2%)     · moonshotai/kimi-k3
security:    $0.7420 · 251,789 in / 5,282 out  · 34,304 cached (14%) · moonshotai/kimi-k3
tests:       $0.0167 · 44,673 in  / 2,712 out  · 0 cached (0%)       · minimax/minimax-m3
commits:     $0.0103 · 22,209 in  / 3,306 out  · 1,536 cached (7%)   · minimax/minimax-m3
description: $0.0162 · 44,999 in  / 2,439 out  · 896 cached (2%)     · minimax/minimax-m3

# Strip comments and blank lines, then join with commas. Refuse to emit an
# empty list: a lane silently running with NO features would look green while
# covering nothing, which is the failure mode this whole guard exists to stop.
LIST="$(sed -e 's/#.*//' -e 's/[[:space:]]//g' "$FILE" | grep -v '^$' | paste -sd, -)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium tests likely

product-features.sh empty-list exit-2 path is not covered by a test

product-features.sh exits 2 if the parsed list is empty or the file is missing, which is the load-bearing behaviour the comment above it claims: "a lane silently running with NO features would look green while covering nothing". The only shell-side test is the shell script and the JS parser agree on the product set, which exercises the happy path with the checked-in file. There is no test that runs product-features.sh against an empty file (or a missing file) and asserts exit 2 — meaning a regression that quietly emits an empty string instead of failing would pass the test suite.

rule Did behaviour change, and does a test now fail if that behaviour regresses? ·

openhuman_core::core::bus::init()
.await
.expect("bus init");
openhuman_core::core::bus::init().await.expect("bus init");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high description confident

Unannounced bus-API rename threaded through tests and src

The diff silently renames crate::core::event_bus to crate::core::bus (with events::DomainEvent moved alongside it) and rewrites call sites across src/core/*, src/openhuman/**, and many tests/*.rs files to match. That is a substantial refactor — the old register_native_global::<...> free function becomes BUS.native().register::<...>, the old synchronous raw_receiver() becomes an async subscription that a new test helper polls. The PR title and body describe none of this. If this is the repair-on-main commit stacked ahead of the gate split, the description needs to call it out, or — better — this part should land separately so the gate split review stays focused.

rule PR body says it splits the default feature list, rewrites the forwarding guard, adds required-featuresto four test targets, adds atui smoke lane, and updates the clippy lane. It does not mention any core-bus rename, API change, or migration. ·

// safe to call pre-boot with no `#[cfg(test)]` guard.
crate::core::event_bus::publish_global(
crate::core::event_bus::DomainEvent::MemoryDriverBindFailed {
crate::core::bus::BUS.publish(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium description uncertain

Guard MemoryDriverBindFailed publishers against the new bus

memory::binding::build calls BUS.publish(...) rather than the old crate::core::event_bus::publish_global(...). The PR body does not call this out, and it is a behaviour-level change: the old call site is documented as a no-op before init, the new one is documented in the surrounding publish_guard_denied comment as the same no-op pre-boot, but the call chain through tinybus::NativeRegistry is different. Reviewers should at least see this called out in the description.

rule The PR is described as a build-time / feature-flag split, but it also rewrites the global event-bus surface. ·

Comment thread Cargo.toml
# gated-off domains, so every CI lane that builds or tests "the product" now
# passes `--features "$(scripts/ci/product-features.sh)"`. If you add a lane,
# decide which of the two sets it is testing and say so.
default = ["media", "skills", "flows", "mcp", "channels", "medulla", "http-server", "scheduler-gate", "file-logging"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium description uncertain

medulla is contributor-default but never on the product list

medulla stays in [features] default (9 contributor gates as claimed) but is absent from scripts/ci/product-features.txt. It is also the only entry besides tui in INTENTIONALLY_NOT_FORWARDED. So every contributor pays for the medulla orchestration-backend client on every edit, and the product never uses it. If the goal was a lean contributor set, leaving medulla in is the opposite of what the PR describes — it should be moved to product-only or dropped, otherwise the package count claimed (356) is over-stated. Confidence is moderate because I cannot see the full dep graph from here.

rule Repository coding rules: "Decide whether a new CI lane tests the contributor or product feature set and say so in a comment; pass --features "$(bash scripts/ci/product-features.sh)" for product lanes." ·

`[features] default` was the product set, so every contributor paid for the
whole product on every edit: web3's ethers/secp256k1 cohort, `documents`'
zstd/bzip2 native builds, the cpal/hound/arboard/enigo/rdev stack behind
`voice`+`inference`, `contacts`' macOS objc2 cohort, `crash-reporting`'s sentry
tree, `tui`'s ratatui. A bare `cargo check` — and therefore rust-analyzer on
every keystroke — resolved 567 packages and ran 7 native C/C++ builds.

`default` is now the CONTRIBUTOR set: 9 gates that cost almost nothing to
compile, so `cargo check` still typechecks nearly the whole tree. Measured:
**567 -> 356 packages, 7 -> 5 native builds.** The kernel floor is untouched at
307/284/5 — this axis does not intersect the `flows` profile.

THIS DOES NOT CHANGE WHAT SHIPS. `app/src-tauri` has declared `openhuman_core`
with `default-features = false` since tinyhumansai#1061 and never inherited `default`; that
is precisely why a forwarding guard had to exist at all.

`check-feature-forwarding.mjs` worked by diffing the shell's forwarding list
against `[features] default`. That is a SUBSET check, and it was sound only
while `default` was the product set. Shrinking `default` makes it weaker with
every gate removed and vacuous if `default` ever empties — silently re-arming
and ~93k Sentry events. Landing the flip without the rewrite would have been
the same bug with a longer fuse.

The product set is now explicit, in `scripts/ci/product-features.txt`, and the
guard asserts three things:

1. the shell forwards EXACTLY that file — set equality, both directions. A
   dropped gate fails on `missing`; a gate the shell grew that the product
   never claimed fails on `unexpected`. Equality cannot pass vacuously.
2. every name in the file is a gate the core actually declares (catches a typo
   or a gate renamed out from under it).
3. every `default` gate is forwarded or allow-listed — the original check,
   retained because it still catches a contributor-set gate nobody shipped.

Two parsers read that file — this one and `product-features.sh`, which the CI
lanes use to build `--features`. A test asserts they agree, because if they
drifted CI would compile a different set than the guard checks.

A lane that relied on default features silently stops covering the product.
Every lane that builds or tests the product now passes
`--features "$(bash scripts/ci/product-features.sh)"`: core clippy, the unit
lane, the changed-files coverage lane, and `scripts/test-rust-with-mock.sh`.
Clippy runs TWICE, once per set — a lint that only fires with the gates off
(an import left unused once a domain is compiled out) is invisible to the
product lane and would be red on every contributor's machine while CI is green.

Four `tests/*.rs` targets name symbols that only exist behind `voice`, `web3`,
`inference` or `crash-reporting`, so they now declare `required-features`.
Without it a bare `cargo test` fails to COMPILE, in files the contributor did
not touch. With it cargo SKIPS them — the same silent-skip trap `--bins`
without `bin-tools` already had, which is why the lane comments spell it out:
`json_rpc_e2e` alone is >12k lines of RPC contract coverage.

`tui` ends up in NEITHER set (default-OFF and deliberately not forwarded), so
nothing in CI would compile it at all. The feature-gate-smoke lane now checks
it explicitly; a future gate in that position needs the same.

Note `required-features` is the blunt instrument — it costs the whole target
when a gate is off, where tinyhumansai#5021's per-symbol `#[cfg]` cleanup would keep the
ungated tests running. Taken deliberately for these four; noted in Cargo.toml.

Verified: contributor `cargo check --lib --tests`, product
`cargo check --all-targets --features <product>`, `--no-default-features
--features tui`, the 27 guard self-tests, the kernel-floor ratchet, and
`cargo fmt --all --check`.

Co-authored-by: Medulla <medulla@tinyhumans.ai>
@coderabbitai coderabbitai Bot added the rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. label Aug 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Around line 358-360: Update the Product row in the feature-set comparison
table to report 16 gates instead of 15, while leaving the other package and
native-build counts unchanged.

In `@scripts/ci/rust-coverage-changed.sh`:
- Around line 40-51: Update the coverage feature setup around PRODUCT_FEATURES
and llvm_cov to include the bin-tools feature, matching the feature gate used by
rsut-core-tests. Ensure llvm_cov passes this augmented feature set for coverage
commands such as --bins, --doc, and --test while preserving the existing
no-feature handling for clean, report, and show-env.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 02340847-f81e-4925-b0ae-7ef9ed521285

📥 Commits

Reviewing files that changed from the base of the PR and between c7e15ba and 722f104.

📒 Files selected for processing (12)
  • .github/workflows/ci-lite.yml
  • .github/workflows/test-reusable.yml
  • AGENTS.md
  • Cargo.toml
  • docs/library-minimal-recipe.md
  • scripts/__tests__/feature-forwarding.test.mjs
  • scripts/ci/check-feature-forwarding.mjs
  • scripts/ci/product-features.sh
  • scripts/ci/product-features.txt
  • scripts/ci/rust-coverage-changed.sh
  • scripts/lib/feature-forwarding.mjs
  • scripts/test-rust-with-mock.sh

Comment thread AGENTS.md
Comment on lines +358 to +360
| **Contributor** | `[features] default` in `Cargo.toml` | What a bare `cargo check`, `cargo test` and rust-analyzer compile. 9 cheap gates. ~356 packages / 5 native builds. |
| **Product** | `scripts/ci/product-features.txt` | What the shipped desktop app has. 15 gates. ~567 packages / 7 native builds. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the product-gate count.

scripts/ci/product-features.txt contains 16 gates, not 15. Update this count so the feature-set contract is accurate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 358 - 360, Update the Product row in the feature-set
comparison table to report 16 gates instead of 15, while leaving the other
package and native-build counts unchanged.

Comment on lines +40 to +51
PRODUCT_FEATURES="$(bash scripts/ci/product-features.sh)"

llvm_cov() {
bash scripts/ci-cancel-aware.sh cargo llvm-cov "$@"
# `clean` and `report` are cargo-llvm-cov subcommands that take no feature
# selection; passing --features to them is an error.
case "${1:-}" in
clean | report | show-env)
bash scripts/ci-cancel-aware.sh cargo llvm-cov "$@"
return
;;
esac
bash scripts/ci-cancel-aware.sh cargo llvm-cov --features "${PRODUCT_FEATURES}" "$@"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 4 '^\[\[bin\]\]|required-features\s*=\s*\["bin-tools"\]' Cargo.toml
rg -n -C 4 'PRODUCT_FEATURES|llvm_cov.*--bins' scripts/ci/rust-coverage-changed.sh

Repository: tinyhumansai/openhuman

Length of output: 3856


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '--- scripts/ci/product-features.sh ---\n'
cat -n scripts/ci/product-features.sh

printf '\n--- relevant coverage script section ---\n'
sed -n '1,130p' scripts/ci/rust-coverage-changed.sh | cat -n

printf '\n--- feature declarations and bin requirements ---\n'
sed -n '1,110p' Cargo.toml | cat -n

printf '\n--- workflow references for coverage features ---\n'
rg -n -C 3 'coverage|rust-coverage-changed|bin-tools|PRODUCT_FEATURES|--bins' .github scripts/ci Cargo.toml

Repository: tinyhumansai/openhuman

Length of output: 251


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- scripts/ci/product-features.sh ---'
cat -n scripts/ci/product-features.sh

printf '%s\n' ''
printf '%s\n' '--- relevant coverage script section ---'
sed -n '1,130p' scripts/ci/rust-coverage-changed.sh | cat -n

printf '%s\n' ''
printf '%s\n' '--- feature declarations and bin requirements ---'
sed -n '1,110p' Cargo.toml | cat -n

printf '%s\n' ''
printf '%s\n' '--- workflow references for coverage features ---'
rg -n -C 3 'coverage|rust-coverage-changed|bin-tools|PRODUCT_FEATURES|--bins' .github scripts/ci Cargo.toml

Repository: tinyhumansai/openhuman

Length of output: 50378


Add bin-tools to coverage feature selection.

[[bin]] targets in Cargo.toml require bin-tools, but PRODUCT_FEATURES comes from scripts/ci/product-features.txt, whose source does not include bin-tools. Use a coverage feature set that includes the same bin-tools gate used by rsut-core-tests so llvm_cov --bins, --doc, and --test do not skip gated library tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ci/rust-coverage-changed.sh` around lines 40 - 51, Update the
coverage feature setup around PRODUCT_FEATURES and llvm_cov to include the
bin-tools feature, matching the feature gate used by rsut-core-tests. Ensure
llvm_cov passes this augmented feature set for coverage commands such as --bins,
--doc, and --test while preserving the existing no-feature handling for clean,
report, and show-env.

@tinysweeper

tinysweeper Bot commented Aug 10, 2026

Copy link
Copy Markdown

What this change touches

12 files, +623 -47 across 7 components. The code graph knows nothing about these files yet — normal for newly added files, and a cold index otherwise.

flowchart LR
  n0["scripts/ci<br/>4 files +194 -19<br/>2 findings"]:::blocking
  n1["scripts/lib<br/>1 file +138 -3"]:::changed
  n2["root<br/>2 files +107 -19<br/>2 findings"]:::flagged
  n3["scripts/__tests__<br/>1 file +116 -0"]:::changed
  n4[".github/workflows<br/>2 files +49 -5"]:::changed
  n5["scripts<br/>1 file +11 -1<br/>1 finding"]:::blocking
  n6["docs<br/>1 file +8 -0"]:::changed
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading

Green: changed. Grey: untouched, reached through an import or a call. Orange: has findings. Red: has a finding that blocks the merge.

Component Files Lines Findings
scripts/ci changed 4 +194 -19 2 (high)
scripts/lib changed 1 +138 -3
(root) changed 2 +107 -19 2 (medium)
scripts/__tests__ changed 1 +116 -0
.github/workflows changed 2 +49 -5
scripts changed 1 +11 -1 1 (high)
docs changed 1 +8 -0
Changed files

scripts/ci

  • scripts/ci/check-feature-forwarding.mjs
  • scripts/ci/product-features.sh
  • scripts/ci/product-features.txt
  • scripts/ci/rust-coverage-changed.sh

scripts/lib

  • scripts/lib/feature-forwarding.mjs

(root)

  • AGENTS.md
  • Cargo.toml

scripts/__tests__

  • scripts/__tests__/feature-forwarding.test.mjs

.github/workflows

  • .github/workflows/ci-lite.yml
  • .github/workflows/test-reusable.yml

scripts

  • scripts/test-rust-with-mock.sh

docs

  • docs/library-minimal-recipe.md

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previously-blocking findings are resolved. Clearing the changes request.

             $0.0107 · 89,494 in / 37,746 out · 67,660 cached (76%) · z-ai/glm-5.2
critique:    $0.0054 · 23,064 in / 21,723 out · 18,086 cached (78%) · z-ai/glm-5.2
security:    $0.0011 · 10,245 in / 3,915 out  · 7,889 cached (77%)  · z-ai/glm-5.2
tests:       $0.0015 · 19,011 in / 4,578 out  · 13,985 cached (74%) · z-ai/glm-5.2
description: $0.0021 · 20,292 in / 6,823 out  · 14,511 cached (72%) · z-ai/glm-5.2

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Aug 10, 2026
@senamakel
senamakel merged commit 722f104 into tinyhumansai:main Aug 10, 2026
33 of 41 checks passed

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: 1 lane(s) blocking, worst finding is high.

Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.

             $0.0183 · 127,792 in / 68,246 out · 98,775 cached (77%) · z-ai/glm-5.2
critique:    $0.0096 · 53,170 in  / 37,403 out · 41,847 cached (79%) · z-ai/glm-5.2
security:    $0.0042 · 35,318 in  / 15,331 out · 27,986 cached (79%) · z-ai/glm-5.2
tests:       $0.0018 · 18,987 in  / 5,548 out  · 13,747 cached (72%) · z-ai/glm-5.2
description: $0.0027 · 20,317 in  / 9,964 out  · 15,195 cached (75%) · z-ai/glm-5.2

# Strip comments and blank lines, then join with commas. Refuse to emit an
# empty list: a lane silently running with NO features would look green while
# covering nothing, which is the failure mode this whole guard exists to stop.
LIST="$(sed -e 's/#.*//' -e 's/[[:space:]]//g' "$FILE" | grep -v '^$' | paste -sd, -)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique confident

Empty-list guard is unreachable: set -e + pipefail aborts before the check

With set -euo pipefail active, the assignment LIST=... runs a pipeline whose middle stage is grep -v '^$'. When product-features.txt contains only comments and blank lines, sed emits only empty lines, so grep -v '^$' finds zero matching lines and exits with status 1. Because pipefail is set, the entire pipeline inherits that non-zero status, and set -e then terminates the script immediately — before line 28 is ever evaluated. The intended guard at lines 28–31 (emit a helpful message, exit 2) is therefore unreachable in the exact scenario it was written to catch; the actual behaviour is a silent exit with code 1. This is also why the empty-list exit-2 path has no test: it cannot be exercised as written. Appending || true to the pipeline (or to the grep stage) would let LIST become the empty string so the explicit check can fire.

[RULE] set -e with pipefail makes the empty-list guard unreachable ·

# x402_twit_sh_live) are silently SKIPPED and the run still exits 0 — the same
# trap `--features bin-tools` already guards for the `src/bin/` targets.
# Source of truth: scripts/ci/product-features.txt.
PRODUCT_FEATURES="$(bash "$REPO_ROOT/scripts/ci/product-features.sh")"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique likely

Guard against an empty PRODUCT_FEATURES before passing it to cargo

The new code captures product-features.sh output into PRODUCT_FEATURES but never checks whether the script succeeded. If product-features.sh exits non-zero (the prior review flagged an uncovered exit-2 path on an empty list) and the shell does not abort (the diff shows no set -e guard here), PRODUCT_FEATURES will be empty and the cargo invocation becomes --features ",bin-tools" — a malformed feature list that either causes a cargo parse error or, if cargo tolerates the leading comma, silently degrades to bin-tools alone. The latter is precisely the "silently SKIPPED, still exits 0" trap the comment says this change exists to prevent. The code should either fail fast on a non-zero exit, or conditionally build the feature list so an empty PRODUCT_FEATURES does not produce a stray leading comma.

[RULE] unhandled-error-paths ·

Comment thread AGENTS.md
Comment on lines +358 to +359
| **Contributor** | `[features] default` in `Cargo.toml` | What a bare `cargo check`, `cargo test` and rust-analyzer compile. 9 cheap gates. ~356 packages / 5 native builds. |
| **Product** | `scripts/ci/product-features.txt` | What the shipped desktop app has. 15 gates. ~567 packages / 7 native builds. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique likely

Gate counts in prose (9 / 15) don't match the table (5 ON / 11 ON)

The introductory text claims the Contributor set has "9 cheap gates" and the Product set has "15 gates", but the table below lists 12 gates total, of which only 5 are marked Contrib ON and 11 are marked Product ON. The numbers cannot be reconciled with the table as shown. Either the table is missing rows for gates that exist in default and/or product-features.txt, or the stated counts are wrong. A contributor reading "9 cheap gates" and then counting ON entries in the Contrib column will be misled either way. If the table is intended to be comprehensive, the counts should match; if it is not, that should be stated.

Suggested change
| **Contributor** | `[features] default` in `Cargo.toml` | What a bare `cargo check`, `cargo test` and rust-analyzer compile. 9 cheap gates. ~356 packages / 5 native builds. |
| **Product** | `scripts/ci/product-features.txt` | What the shipped desktop app has. 15 gates. ~567 packages / 7 native builds. |
Make the table's ON-counts match the prose counts, or add the missing gate rows, or clarify that the table is not exhaustive.

[RULE] N/A ·

# measuring code that ships, and the diff-coverage gate would pass a PR whose
# changed lines were never compiled. Source of truth:
# scripts/ci/product-features.txt.
PRODUCT_FEATURES="$(bash scripts/ci/product-features.sh)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique uncertain

Guard against empty product-features output before passing --features

The output of product-features.sh is captured into PRODUCT_FEATURES with no check that it is non-empty. If that script ever returns empty output (exit 0, no stdout), the downstream call becomes cargo llvm-cov --features "", which is equivalent to the default contributor feature set — precisely the situation the added comment warns against. In that case the diff-coverage gate would pass a PR whose changed lines in gated domains were never compiled, and there would be no loud failure to signal it. An explicit guard such as [ -n "$PRODUCT_FEATURES" ] after the assignment would make the failure audible.

Suggested change
PRODUCT_FEATURES="$(bash scripts/ci/product-features.sh)"
Guard against empty output, e.g. add `if [ -z "$PRODUCT_FEATURES" ]; then log "product-features.sh returned no features"; exit 1; fi` immediately after the assignment.

**[RULE] ** ·

Comment thread Cargo.toml
# gated-off domains, so every CI lane that builds or tests "the product" now
# passes `--features "$(scripts/ci/product-features.sh)"`. If you add a lane,
# decide which of the two sets it is testing and say so.
default = ["media", "skills", "flows", "mcp", "channels", "medulla", "http-server", "scheduler-gate", "file-logging"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium security likely

Move medulla out of the contributor default or onto the product list

existing_code = default = ["media", "skills", "flows", "mcp", "channels", "medulla", "http-server", "scheduler-gate", "file-logging"]

This PR is the one that explicitly splits the contributor default from the product set. The comment block says the product set lives in scripts/ci/product-features.txt and is forwarded by the desktop shell. The prior review established that medulla is not in that product file. This PR keeps medulla in the contributor default while it is absent from the product set, so contributors and rust-analyzer typecheck and test medulla-gated code paths that the shipped binary never includes. The result is a coverage gap: CI lanes that run cargo test (default features) exercise medulla-gated logic, while the product lanes that pass --features "$(scripts/ci/product-features.sh)" do not, so medulla-gated code can ship untested. If medulla gates security-relevant logic, the product binary takes paths no contributor lane has validated.

[RULE] Prior finding still standing ·

@tinysweeper tinysweeper Bot added priority: p1 Next. Wrong behaviour a user will hit, or a security weakness behind a condition. and removed priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. labels Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p1 Next. Wrong behaviour a user will hit, or a security weakness behind a condition. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant