You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ci: routing, lint and parity gates, and upgrade-harness gaps #2070
This issue lists gaps in CI routing, lint and parity gates, and the upgrade test harness. Each item was checked against main at 29fbd7152 on 2026-09-30.
CI routing and gates
The cross-version upgrade scenarios never run in CI (medium).
What happens: tests/integration/upgrade.sh supports the all, mixed, late, rollback and mixed-dkg scenarios (:38). CI runs only base and persist (.github/workflows/ci.yml:795), and no workflow provides the old and new binaries.
Fix: add a release-candidate job that builds the previous release and the candidate, then runs the upgrade scenarios.
Circuit CI routing misses the builder, the artifact store and some verifier inputs (medium).
What happens: the circuit path filter (ci.yml:101) omits scripts/build-circuits.ts, scripts/circuit-artifacts.ts, the Honk verifier paths and scripts/protocol/constants.ts. build_circuits is also computed without the contracts filter (:251). A pull request that changes only one of these files skips the circuit and verifier-sync checks. Release-candidate runs force them (:213).
Both end-to-end jobs are conditional (low).
What happens: crisp_e2e (ci.yml:1079) and template_integration (:1654) run only when their path filters match. If they are required checks, a skipped job does not show whether the covered code still works.
Fix: add one result job that always runs and fails when a covering job fails or is skipped unexpectedly.
No Clippy in CI (low).
What happens: rust:lint is cargo fmt -- --check (package.json:43, ci.yml:321). Nothing runs Clippy.
Root ESLint runs only in the pre-push hook (low).
What happens: pnpm lint (package.json:25) runs from .husky/pre-push:3. CI runs only the Solidity linter (ci.yml:647).
The address-consumer check runs only in the pre-push hook (low).
What happens: check:addresses (package.json:33, scripts/check-addresses.ts:22) finds stale addresses in docs, dashboards and examples. CI checks only that the manifest is fresh (ci.yml:663).
The agent-harness job skips the committee parity matrix (low).
What happens: .github/workflows/agent-harness.yml:34 runs scripts/check-committee.sh without Nargo or the generator. The script then prints a skip note and passes (check-committee.sh:433-460). The circuit job's rebuild covers only the default committee (ci.yml:1279-1283).
Solhint allows 10 warnings (info).
What happens: packages/interfold-contracts/package.json:192 sets --max-warnings 10. Lower it to zero if a zero-warning policy is wanted.
The doc-sync check accepts any harness document change (low).
What happens:
scripts/check-doc-sync.sh:86 passes as soon as any file in the harness document set changed. That file need not be the one that describes the changed code.
The watch list (:32) omits crates that the harness documents, including data-availability, support and sdk.
agent/invariants/00_INDEX.md:39 routes data-availability work to the protocol and flow-trace documents.
The invariant-reviewer agent has unrestricted shell access (low).
What happens: .claude/agents/invariant-reviewer.md:4 grants Bash with no command allowlist, although the prompt says the reviewer is read-only (agent/prompts/invariant-reviewer.md:8).
E3 stage and failure ordinals (Solidity, Rust, SDK, dashboard);
the message width of 100 (Noir, Rust, the verifiers, the CRISP SDK and the program);
the dashboard's committee thresholds (packages/interfold-dashboard/src/lib/e3.ts:69, which check-committee.sh does not cover);
the proof-layout offsets. test/BfvVkBindingIntegration.spec.ts:340 skips verification for stale fixture lengths instead of failing.
Integration harness
fns.sh kills every process that matches anvil (medium).
What happens: tests/integration/fns.sh:62 and :269 run pkill -9 -f "anvil", and :341 calls kill_em_all when the file is sourced. Any Anvil on the machine dies, including one in another checkout. The helper also matches broad interfold and hardhat patterns.
Fix: record child PIDs, or use a dedicated process group, and stop only those.
What happens: tests/integration/upgrade.sh:351 fails only on a few crash patterns. A run with an unexpected error, a rejected share or an accusation can still pass.
Fix: classify errors per phase, with a narrow allowlist of expected transient ones.
Rollback is tested before the ciphertext exists, not after a share was sent (low).
What happens: the rollback happens 30 seconds after effects are enabled (upgrade.sh:275), and the ciphertext is published later (:315). No scenario rolls back a node that has already sent its decryption share.
No backup-and-restore scenario (low).
What happens: upgrade.sh:110 records a file inventory (tests/integration/lib/state_inventory.py:60), not a restorable backup. Nothing restores a stopped node from a backup and checks that it completes.
The upgrade scenarios fix the committee (low).
What happens: the local randomness word has no seed override (packages/interfold-contracts/tasks/interfold.ts:153), so every run selects the same members and aggregator.
Fix: add a local-only seed input, and run a small seed matrix.
No scale profile for the upgrade harness (low).
What happens: upgrade.sh uses one fixed configuration (:47) and candidate assignment (:51). It has no production-sized, secure-parameter profile, and no variant that starts from v0.17.0.
Fix: add scale and old-version options to upgrade.sh, not separate scripts.
Local test chains disable the clock-drift fence (low).
What happens: crates/ciphernode-builder/src/ciphernode_builder.rs:1534 removes the limit when every chain is 1337 or 31337, so the integration suite (tests/integration/interfold.config.yaml:4) never runs with the production drift policy. The public-chain case has a unit test (:2286).
No tool shows the per-E3 state of a stopped node (low).
What happens: crates/entrypoint/src/validate.rs checks schema, cursors and open loops. Nothing reports, per E3, the stage, whether the keyshare snapshot loads, or the recovery status. That is what an operator needs before an upgrade.
Test coverage
Released event, wire and snapshot layouts have few fixed-byte fixtures (medium).
What exists: fixed-byte fixtures cover bond-owner state (crates/sortition/src/sortition/bond_owners.rs:111), one CommitteeFinalized variant, node-role bytes, and a sync envelope with a u64 payload (crates/net/src/network_sync/wire.rs:297).
What is missing: fixtures for the other InterfoldEventData variants, the real sync payloads, the gossip envelope (wire.rs:36), and the keyshare and aggregation recovery snapshots.
Why it matters: v0.19 must keep all of these byte-compatible, and a field or variant reordering would not fail any test.
The DHT prune tests check a copied closure (low).
What happens: crates/net/src/net_interface.rs:2060, :2094 and :2147 build a standalone store and run a copy of the retain logic. Removing the production call (prune_expired_dht_records, :1652, used at :1710 and in the periodic loop) would not fail them.
DKG-start paths in the keyshare actor lack failure-driven tests (low). All the tests below are in crates/keyshare/src/threshold_keyshare/tests.rs.
Pending timing read: no test holds the timing read pending while peer inputs arrive. The early-selection helper's reader resolves at once (:532).
Timing-read error: nothing injects a failed read to cover the retry (handlers.rs:153-163).
Initialization: nothing injects a failure in either path, the cached-timing one (handlers.rs:136) or the fresh-read one (:161-165). So idempotency is untested: one keypair, no duplicate collectors or key intents.
Replay: effects-disabled replay of the early inputs through ReplaySpool and the production effect gates is not covered (:749-800 seed state instead).
Release tooling
The dashboard's update check ignores pre-release ordering (low).
What happens: crates/dashboard/src/updates.rs:168-176 keeps only three numeric parts. 0.19.0-dev.N and 0.19.0 then compare as equal, and dev.2 against dev.10 is not compared correctly.
Release-channel tests stop short of the full wiring (low).
What happens: scripts/release.test.mjs covers publication gating. Nothing exercises prepareRelease and prepareReleaseAssets end to end for a stable candidate and a dev candidate, including the channel outputs, the npm tags and the pre-release install commands.
Summary
This issue lists gaps in CI routing, lint and parity gates, and the upgrade test harness. Each item was checked against
mainat29fbd7152on 2026-09-30.CI routing and gates
tests/integration/upgrade.shsupports theall,mixed,late,rollbackandmixed-dkgscenarios (:38). CI runs onlybaseandpersist(.github/workflows/ci.yml:795), and no workflow provides the old and new binaries.ci.yml:101) omitsscripts/build-circuits.ts,scripts/circuit-artifacts.ts, the Honk verifier paths andscripts/protocol/constants.ts.build_circuitsis also computed without the contracts filter (:251). A pull request that changes only one of these files skips the circuit and verifier-sync checks. Release-candidate runs force them (:213).crisp_e2e(ci.yml:1079) andtemplate_integration(:1654) run only when their path filters match. If they are required checks, a skipped job does not show whether the covered code still works.rust:lintiscargo fmt -- --check(package.json:43,ci.yml:321). Nothing runs Clippy.pnpm lint(package.json:25) runs from.husky/pre-push:3. CI runs only the Solidity linter (ci.yml:647).check:addresses(package.json:33,scripts/check-addresses.ts:22) finds stale addresses in docs, dashboards and examples. CI checks only that the manifest is fresh (ci.yml:663)..github/workflows/agent-harness.yml:34runsscripts/check-committee.shwithout Nargo or the generator. The script then prints a skip note and passes (check-committee.sh:433-460). The circuit job's rebuild covers only the default committee (ci.yml:1279-1283).packages/interfold-contracts/package.json:192sets--max-warnings 10. Lower it to zero if a zero-warning policy is wanted.scripts/check-doc-sync.sh:86passes as soon as any file in the harness document set changed. That file need not be the one that describes the changed code.:32) omits crates that the harness documents, includingdata-availability,supportandsdk.agent/invariants/00_INDEX.md:39routes data-availability work to the protocol and flow-trace documents.invariant-revieweragent has unrestricted shell access (low)..claude/agents/invariant-reviewer.md:4grantsBashwith no command allowlist, although the prompt says the reviewer is read-only (agent/prompts/invariant-reviewer.md:8).packages/interfold-dashboard/src/lib/e3.ts:69, whichcheck-committee.shdoes not cover);test/BfvVkBindingIntegration.spec.ts:340skips verification for stale fixture lengths instead of failing.Integration harness
fns.shkills every process that matchesanvil(medium).tests/integration/fns.sh:62and:269runpkill -9 -f "anvil", and:341callskill_em_allwhen the file is sourced. Any Anvil on the machine dies, including one in another checkout. The helper also matches broadinterfoldandhardhatpatterns.ERRORlines (low).tests/integration/upgrade.sh:351fails only on a few crash patterns. A run with an unexpected error, a rejected share or an accusation can still pass.upgrade.sh:275), and the ciphertext is published later (:315). No scenario rolls back a node that has already sent its decryption share.upgrade.sh:110records a file inventory (tests/integration/lib/state_inventory.py:60), not a restorable backup. Nothing restores a stopped node from a backup and checks that it completes.packages/interfold-contracts/tasks/interfold.ts:153), so every run selects the same members and aggregator.upgrade.shuses one fixed configuration (:47) and candidate assignment (:51). It has no production-sized, secure-parameter profile, and no variant that starts from v0.17.0.upgrade.sh, not separate scripts.crates/ciphernode-builder/src/ciphernode_builder.rs:1534removes the limit when every chain is 1337 or 31337, so the integration suite (tests/integration/interfold.config.yaml:4) never runs with the production drift policy. The public-chain case has a unit test (:2286).crates/entrypoint/src/validate.rschecks schema, cursors and open loops. Nothing reports, per E3, the stage, whether the keyshare snapshot loads, or the recovery status. That is what an operator needs before an upgrade.Test coverage
crates/sortition/src/sortition/bond_owners.rs:111), oneCommitteeFinalizedvariant, node-role bytes, and a sync envelope with au64payload (crates/net/src/network_sync/wire.rs:297).InterfoldEventDatavariants, the real sync payloads, the gossip envelope (wire.rs:36), and the keyshare and aggregation recovery snapshots.crates/net/src/net_interface.rs:2060,:2094and:2147build a standalone store and run a copy of the retain logic. Removing the production call (prune_expired_dht_records,:1652, used at:1710and in the periodic loop) would not fail them.crates/keyshare/src/threshold_keyshare/tests.rs.:532).handlers.rs:153-163).handlers.rs:136) or the fresh-read one (:161-165). So idempotency is untested: one keypair, no duplicate collectors or key intents.:652-686), not disk hydration throughThresholdKeyshareExtension::hydrate(crates/keyshare/src/ext.rs:132).ReplaySpooland the production effect gates is not covered (:749-800seed state instead).Release tooling
crates/dashboard/src/updates.rs:168-176keeps only three numeric parts.0.19.0-dev.Nand0.19.0then compare as equal, anddev.2againstdev.10is not compared correctly.scripts/release.test.mjscovers publication gating. Nothing exercisesprepareReleaseandprepareReleaseAssetsend to end for a stable candidate and a dev candidate, including the channel outputs, the npm tags and the pre-release install commands.