{"record":{"id":"ea9dd08a75af42b1","repo":"nexu-io/open-design","slug":"od-critique-score-threshold-scorethreshold-mu","errorCode":null,"errorMessage":"OD_CRITIQUE_SCORE_THRESHOLD (${scoreThreshold}) must be <= OD_CRITIQUE_SCORE_SCALE (${scoreScale})","messagePattern":"OD_CRITIQUE_SCORE_THRESHOLD \\((.+?)\\) must be <= OD_CRITIQUE_SCORE_SCALE \\((.+?)\\)","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/critique/config.ts","lineNumber":26,"sourceCode":" * surfaces at boot, never silently.\n *\n * @see specs/current/critique-theater.md § Configuration (env vars)\n */\nexport function loadCritiqueConfigFromEnv(env: NodeJS.ProcessEnv = process.env): CritiqueConfig {\n  const defaults = defaultCritiqueConfig();\n\n  const enabled = parseEnabled(env['OD_CRITIQUE_ENABLED'], defaults.enabled);\n  const maxRounds = parsePositiveInt('OD_CRITIQUE_MAX_ROUNDS', env['OD_CRITIQUE_MAX_ROUNDS'], defaults.maxRounds);\n  const scoreThreshold = parseNonNegativeFloat('OD_CRITIQUE_SCORE_THRESHOLD', env['OD_CRITIQUE_SCORE_THRESHOLD'], defaults.scoreThreshold);\n  const scoreScale = parsePositiveInt('OD_CRITIQUE_SCORE_SCALE', env['OD_CRITIQUE_SCORE_SCALE'], defaults.scoreScale);\n  const perRoundTimeoutMs = parsePositiveInt('OD_CRITIQUE_PER_ROUND_TIMEOUT_MS', env['OD_CRITIQUE_PER_ROUND_TIMEOUT_MS'], defaults.perRoundTimeoutMs);\n  const totalTimeoutMs = parsePositiveInt('OD_CRITIQUE_TOTAL_TIMEOUT_MS', env['OD_CRITIQUE_TOTAL_TIMEOUT_MS'], defaults.totalTimeoutMs);\n  const parserMaxBlockBytes = parsePositiveInt('OD_CRITIQUE_PARSER_MAX_BLOCK_BYTES', env['OD_CRITIQUE_PARSER_MAX_BLOCK_BYTES'], defaults.parserMaxBlockBytes);\n  const fallbackPolicy = parseFallbackPolicy(env['OD_CRITIQUE_FALLBACK_POLICY'], defaults.fallbackPolicy);\n\n  // Cross-field validation: threshold cannot exceed scale.\n  if (scoreThreshold > scoreScale + 1e-9) {\n    throw new RangeError(\n      `OD_CRITIQUE_SCORE_THRESHOLD (${scoreThreshold}) must be <= OD_CRITIQUE_SCORE_SCALE (${scoreScale})`,\n    );\n  }\n\n  return {\n    ...defaults,\n    enabled,\n    maxRounds,\n    scoreThreshold,\n    scoreScale,\n    perRoundTimeoutMs,\n    totalTimeoutMs,\n    parserMaxBlockBytes,\n    fallbackPolicy,\n  };\n}\n\n// ---------------------------------------------------------------------------","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/critique/config.ts#L8-L44","documentation":"Thrown by loadCritiqueConfigFromEnv() as a RangeError when OD_CRITIQUE_SCORE_THRESHOLD is greater than OD_CRITIQUE_SCORE_SCALE (with a 1e-9 tolerance). It is a cross-field validation: scores produced by the panel are scaled against scoreScale, so a threshold above the scale would be unreachable and every run would be flagged sub-threshold. The check runs at config load so misconfiguration surfaces at boot, never silently.","triggerScenarios":"Setting OD_CRITIQUE_SCORE_THRESHOLD to a value larger than OD_CRITIQUE_SCORE_SCALE in the environment — e.g. THRESHOLD=90 with SCALE=10.","commonSituations":"Operator confused scale (e.g. 0-10 vs 0-100); copy-pasted env from another deployment with a different scale; threshold bumped without scaling the scale to match.","solutions":["Set OD_CRITIQUE_SCORE_THRESHOLD <= OD_CRITIQUE_SCORE_SCALE (e.g. threshold 8 with scale 10, or threshold 80 with scale 100).","Decide on one scale convention (0-10 or 0-100) and set both vars consistently.","Leave both unset to use defaultCritiqueConfig(), which already satisfies the constraint."],"exampleFix":"# before: threshold above scale\nOD_CRITIQUE_SCORE_THRESHOLD=90\nOD_CRITIQUE_SCORE_SCALE=10\n\n# after: keep threshold within scale\nOD_CRITIQUE_SCORE_THRESHOLD=8\nOD_CRITIQUE_SCORE_SCALE=10","handlingStrategy":"validation","validationCode":"const threshold = Number(process.env.OD_CRITIQUE_SCORE_THRESHOLD ?? defaultThreshold);\nconst scale = Number(process.env.OD_CRITIQUE_SCORE_SCALE ?? defaultScale);\nif (threshold > scale + 1e-9) {\n  throw new RangeError(`threshold ${threshold} must be <= scale ${scale}`);\n}","typeGuard":"function thresholdWithinScale(threshold: number, scale: number): boolean {\n  return threshold <= scale + 1e-9;\n}","tryCatchPattern":"try { loadCritiqueConfigFromEnv(); }\ncatch (e) {\n  if (e instanceof RangeError && /SCORE_THRESHOLD/.test(e.message)) {\n    // log, fall back to defaults, or fail boot per policy\n  } else throw e;\n}","preventionTips":["Document the scale convention (0-10 vs 0-100) next to both env vars.","Add a config-lint check in CI that asserts threshold <= scale for templated env files.","Use defaultCritiqueConfig() unless you deliberately need to override both."],"tags":["critique","config","env-vars","range-error","boot"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}