Skip to content

feat(sat): multiple active challenges per (family, tier) with mixed difficulty #241

Description

@wallscaler

Context

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
  • _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
  • 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.

Out of scope (separate work)

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
  • Public projection includes difficulty_label

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions