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
Today the schema constraint is one active row per (family, tier) via active_scope='tier' (see SqliteChallengeSource.activate). That means at any moment T1 has exactly one active challenge, T2 exactly one, T3 exactly one — three live total.
This is a useful invariant for routing ("the" current challenge per tier), but it limits experimentation:
We can't run an easy 3-byte T1 alongside a harder 6-byte T1 to see how miners self-select on compute vs. payout
We can't A/B test new difficulty profiles without retiring the existing one
We can't keep a backstop "warm-up" challenge always live while harder ones cycle through
Proposal
Lift the 1-per-tier cap so multiple challenges of the same (family, tier) can be active simultaneously, distinguished by an additional dimension — proposed difficulty_label (TEXT, indexed alongside tier).
Schema (idempotent migration)
```sql
ALTER TABLE lane_challenges ADD COLUMN difficulty_label TEXT;
CREATE INDEX IF NOT EXISTS idx_lane_challenges_tier_diff
ON lane_challenges(family_id, tier, difficulty_label, status);
```
New active scope
Add `active_scope='tier_difficulty'`: one active row per (family, tier, difficulty_label). Backward-compat: rows with difficulty_label IS NULL continue to obey active_scope='tier' (one per tier).
What needs to change
SqliteChallengeSource.activate — honor the new scope value
SqliteChallengeSource.get_active_for_tier — needs a difficulty arg or returns a list
Public API: /v1/synthetic-boolean/active-challenges needs to return multiple rows per tier, and /v1/synthetic-boolean/current-challenge?tier=N needs a difficulty arg
Site UI: tier card splits into difficulty subcards
Autopilot: _count_cathedral_pending_per_combo already aggregates by (tier, kind); needs to also key on difficulty_label
Generator side: producer config gains FRED_BSAT_AUTOGENERATE_DIFFICULTY_LABEL so the generated CNF self-reports which difficulty bucket it belongs to
Why now
Triggered by 2026-05-27 deploy: the autopilot + generator chain works end-to-end, but T1 sha256_preimage 3-byte is solvable in seconds. The natural fix "bump preimage_bytes to 6" would make T1 quiet for hours at a time. Multi-active-per-tier lets us keep easy challenges live for liveness while harder ones cycle through for real compute signal.
Multi-producer support in Serge's generator — needs its own ticket in fred-bsat
Acceptance
Two rows with (family='synthetic_boolean_v1', tier=1, difficulty_label='3b') and (... tier=1, difficulty_label='6b') can both be status='active' at the same time
Solving one does NOT auto-retire the other
recent-wins distinguishes which difficulty was solved
Context
Today the schema constraint is one active row per
(family, tier)viaactive_scope='tier'(seeSqliteChallengeSource.activate). That means at any moment T1 has exactly one active challenge, T2 exactly one, T3 exactly one — three live total.This is a useful invariant for routing ("the" current challenge per tier), but it limits experimentation:
Proposal
Lift the 1-per-tier cap so multiple challenges of the same
(family, tier)can be active simultaneously, distinguished by an additional dimension — proposeddifficulty_label(TEXT, indexed alongside tier).Schema (idempotent migration)
```sql
ALTER TABLE lane_challenges ADD COLUMN difficulty_label TEXT;
CREATE INDEX IF NOT EXISTS idx_lane_challenges_tier_diff
ON lane_challenges(family_id, tier, difficulty_label, status);
```
New active scope
Add `active_scope='tier_difficulty'`: one active row per
(family, tier, difficulty_label). Backward-compat: rows withdifficulty_label IS NULLcontinue to obeyactive_scope='tier'(one per tier).What needs to change
SqliteChallengeSource.activate— honor the new scope valueSqliteChallengeSource.get_active_for_tier— needs a difficulty arg or returns a list_handle_solve_post/atomic_claim_winner— currently lock by(family_id, challenge_id)so this is fine, but the "next pending" promotion (PR fix(submit): auto-promote next pending after direct-submit win #235) needs to scope to the same difficulty_label/v1/synthetic-boolean/active-challengesneeds to return multiple rows per tier, and/v1/synthetic-boolean/current-challenge?tier=Nneeds adifficultyarg_count_cathedral_pending_per_comboalready aggregates by(tier, kind); needs to also key ondifficulty_labelFRED_BSAT_AUTOGENERATE_DIFFICULTY_LABELso the generated CNF self-reports which difficulty bucket it belongs toWhy now
Triggered by 2026-05-27 deploy: the autopilot + generator chain works end-to-end, but T1 sha256_preimage 3-byte is solvable in seconds. The natural fix "bump preimage_bytes to 6" would make T1 quiet for hours at a time. Multi-active-per-tier lets us keep easy challenges live for liveness while harder ones cycle through for real compute signal.
Out of scope (separate work)
difficulty_label— that's Tier-aware scoring: score_multiplier on lane_challenges + weight policy honors it #236 territory (tier-aware scoring), should extend to difficulty as wellfred-bsatAcceptance
(family='synthetic_boolean_v1', tier=1, difficulty_label='3b')and(... tier=1, difficulty_label='6b')can both bestatus='active'at the same timerecent-winsdistinguishes which difficulty was solveddifficulty_label