pbakaus/impeccable · error · Error

concept-seed: every challenger tier needs at least one…

Error message

concept-seed: every challenger tier needs at least one approved concept

What it means

Thrown during concept-seed roll selection when any of the well tiers (graphic, interaction, atmosphere) has zero approved concepts in the pool. The roll draws challengers per tier so that every candidate set mixes all three registers; an empty tier cannot be sampled around, so the roll refuses rather than silently starving one tier and pretending the hand is balanced.

Solutions

  1. Review and approve at least one concept in each tier (graphic, interaction, atmosphere) so every tier has an approved entry.
  2. Re-run the review flow on the existing catalog to convert rejected/pending candidates into approved ones for the missing tier.
  3. Generate additional candidates for the thin tier first, then approve one, then re-run the roll.
  4. Check that aggressive --min-rating or mode filters are not emptying a tier upstream of the roll.

Example fix

# before: pool has approved graphic+interaction only
node skill/scripts/concept-seed.mjs --scope direction --from <key>   # throws

# after: approve one atmosphere-tier concept via the review flow, then
node skill/scripts/concept-seed.mjs --scope direction --from <key>
Defensive patterns

Strategy: validation

Validate before calling

import { WELL_TIERS } from './lib/roll-selection.mjs';
function poolCoversAllTiers(approved) {
  const tiers = new Set(approved.map(c => c.wellTier));
  return WELL_TIERS.every(t => tiers.has(t));
}
if (!poolCoversAllTiers(approvedConcepts)) {
  throw new Error(`approve at least one concept per tier before rolling: missing ${
    WELL_TIERS.filter(t => !approvedConcepts.some(c => c.wellTier === t)).join(', ')}`);
}

Type guard

function isRollablePool(approved) {
  return ['graphic', 'interaction', 'atmosphere'].every(tier =>
    approved.some(c => c.wellTier === tier));
}

Prevention

When it happens

Trigger: Running concept-seed with --from <key> where the reviewed catalog approved only graphic-tier concepts; a fresh project whose approved pool has one concept each for interaction and atmosphere but none for graphic; minRating or mode filters configured upstream that emptied one tier's approved set before this check runs.

Common situations: Early in a project when only a few concepts have been human-approved; after a review session that rejected all concepts of one tier; switching scope (direction vs surface) against a pool that was only populated for the other scope.

Related errors


AI-assisted analysis of pbakaus/impeccable@f88b2837a7 (2026-08-18). Data as JSON: /api/errors/21cc1d99e761e1fd. Report an issue: GitHub.

Appendix: source

Thrown at skill/scripts/lib/roll-selection.mjs:170

export function* selectApprovedChallengers({ scope, key, reroll = 0, minRating = null, mode = null, concepts }) {
  const approved = concepts.filter(concept => concept.status === 'approved');
  // Direction chooses a durable identity, so it draws worlds; surface designs
  // one page inside a committed identity, so it draws compositions. Duals serve
  // both. A tier with no matching-strength approvals falls back to its full
  // approved pool rather than starving the roll.
  const wanted = scope === 'direction'
    ? new Set(['world', 'dual'])
    : new Set(['composition', 'dual']);

  const approvedByTier = new Map();
  for (const concept of approved) {
    const tier = approvedByTier.get(concept.wellTier) || [];
    tier.push(concept);
    approvedByTier.set(concept.wellTier, tier);
  }
  if (WELL_TIERS.some(tier => !(approvedByTier.get(tier) || []).length)) {
    throw new Error('concept-seed: every challenger tier needs at least one approved concept');
  }

  // Optional minimum-rating gate, applied per tier and skipped for any tier it
  // would empty, so a thin tier degrades to its full approved pool.
  if (minRating) {
    for (const [tier, pool] of approvedByTier) {
      const rated = pool.filter(concept => (concept.review?.rating || 0) >= minRating);
      if (rated.length > 0) approvedByTier.set(tier, rated);
    }
  }
  // Mode eligibility, per tier and skipped where it would empty a tier. Worlds
  // used to be drawn with no mode awareness at all, so a build asking for an app
  // UI could get six worlds that only make sense on a landing page. A world is an
  // identity and identities transfer further than compositions do, so this is a
  // ceiling the reviewer sets rather than a category assignment: eligible
  // everywhere until someone says otherwise.
  if (mode) {
    for (const [tier, pool] of approvedByTier) {

View on GitHub (pinned to f88b2837a7)