A functional toolkit for authoring resource-leveling algorithms — compose constraint and scoring blocks, enumerate feasible schedules
0

Configure Feed

Select the types of activity you want to include in your feed.

fix(analyzeRun): skip disabled constraints instead of erroring on them

analyzeRun ran every check over enabled: false constraints, so a disabled
infeasible deadline (or dangling reference, or cycle-forming precedence)
produced error-severity issues and runFromConfig threw RunAnalysisError —
blocking exactly the what-if ablation workflow the enabled lever exists
for. Analysis now covers the same active set the search preprocesses;
duplicate-name counting still spans all carried instances.

Russ T. Fugal (Jul 16, 2026, 2:11 AM -0600) a4981456 6c22c30e

+52 -1
+8 -1
src/level-core/analyzeRun.ts
··· 77 77 const source = constraintLabel(c); 78 78 if (c.name !== undefined) seenNames.set(c.name, (seenNames.get(c.name) ?? 0) + 1); 79 79 80 + // `enabled: false` constraints are carried in the run but not enforced — 81 + // searches skip them — so nothing about them can invalidate the run. 82 + // Reference, feasibility, and cycle checks all analyze only the enabled 83 + // set (the same set serialSGS preprocesses); a disabled constraint that 84 + // would be infeasible is exactly the what-if ablation case. 85 + if (c.enabled === false) continue; 86 + 80 87 switch (c.kind) { 81 88 case "Precedence": 82 89 for (const e of c.edges) { ··· 156 163 } 157 164 } 158 165 159 - if (c.enabled !== false && opts?.search?.supports && !opts.search.supports(c.kind)) { 166 + if (opts?.search?.supports && !opts.search.supports(c.kind)) { 160 167 issues.push({ 161 168 code: "LEVELSET_W201", 162 169 severity: "warning",
+15
test/level-blocks/runConfig.test.ts
··· 125 125 expect(run.constraints[0]!.enabled).toBe(false); 126 126 }); 127 127 128 + test("a disabled constraint that would fail analysis does not block the run", async () => { 129 + // Disabling an infeasible deadline is the ablation workflow: the run 130 + // must build and schedule, not throw RunAnalysisError. 131 + const config = baseConfig(); 132 + config.pipeline.blocks.push({ 133 + instanceId: "impossible-gate", 134 + code: STANDARD_CODES.deadline, 135 + params: { taskUniqueId: 1, latestFinish: 0 }, 136 + enabled: false, 137 + }); 138 + const run = runFromConfig(crewProject(), config); 139 + expect(run.analysis.ok).toBe(true); 140 + expect(await run.best()).not.toBeNull(); 141 + }); 142 + 128 143 test("filled config round-trips: running it again reproduces the schedule", async () => { 129 144 const run1 = runFromConfig(crewProject(), baseConfig()); 130 145 const best1 = await run1.best();
+29
test/level-core/analyzeRun.test.ts
··· 100 100 expect(codes(analysis.issues)).toContain("LEVELSET_E105"); 101 101 }); 102 102 103 + test("disabled constraints are carried but not analyzed — no errors from the skipped set", () => { 104 + // Each of these would be an error if enabled; disabled they are exactly 105 + // the what-if ablation case and must not invalidate the run. 106 + const analysis = analyzeRun(resolvedFixture(), [ 107 + { kind: "Deadline", taskUniqueId: 1, latestFinish: 0, enabled: false, name: "ablated" }, 108 + { kind: "MaxConcurrentResource", resourceUniqueId: 999, max: 1, enabled: false }, 109 + { 110 + kind: "Precedence", 111 + enabled: false, 112 + edges: [ 113 + { 114 + predecessorUniqueId: 1, 115 + successorUniqueId: 2, 116 + type: RelationType.FinishToStart, 117 + lagDays: 0, 118 + }, 119 + { 120 + predecessorUniqueId: 2, 121 + successorUniqueId: 1, 122 + type: RelationType.FinishToStart, 123 + lagDays: 0, 124 + }, 125 + ], 126 + }, 127 + ]); 128 + expect(analysis.ok).toBe(true); 129 + expect(analysis.issues).toHaveLength(0); 130 + }); 131 + 103 132 test("unit precedence cycles are LEVELSET_E106", () => { 104 133 const analysis = analyzeRun(resolvedFixture(), [ 105 134 { kind: "UnitPrecedence", unitId: 10, afterUnitIds: [20] },