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.

feat(level-blocks): standard registry with versioned block codes

standardRegistry() curates every shipping block under a versioned code
(levelset.PeakCap.v1). Versioned names are the forward-compatibility
seam: semantic changes ship as .v2 while .v1 keeps resolving, so saved
run configs stay reproducible across releases. createRegistry remains
singleton-free; apps extend via standardRegistry(extraDefs) or keep
building their own.

Russ T. Fugal (Jul 16, 2026, 12:01 AM -0600) 936b87fe 166169a7

+189
+1
src/leveling.ts
··· 68 68 export * from "./level-blocks/registry.ts"; 69 69 export * from "./level-blocks/graph.ts"; 70 70 export * from "./level-blocks/compile.ts"; 71 + export * from "./level-blocks/standard.ts"; 71 72 72 73 // ── Blocks (configured constraints & scorers + MiniZinc fragments) ─── 73 74 export * from "./level-blocks/types.ts";
+131
src/level-blocks/standard.ts
··· 1 + // The standard block registry — every shipping block under a *versioned* 2 + // registry code (`levelset.PeakCap.v1`). Versioned names are the 3 + // forward-compatibility seam: when a block's semantics change, ship a `.v2` 4 + // and keep `.v1` resolving, so a run config saved in March still produces 5 + // the same schedule in September. Renames get an alias entry, not a break. 6 + // 7 + // `createRegistry` stays side-effect-free and singleton-less; this module 8 + // just curates the defs. Apps with their own catalogs (codes, tiers, 9 + // shadows) keep building their own registries — or extend this one via 10 + // `standardRegistry(extraDefs)`. 11 + 12 + import { ConcurrentResourceCostBlock } from "./concurrentResourceCost.ts"; 13 + import { ConcurrentUnitsLimitBlock } from "./concurrentUnitsLimit.ts"; 14 + import { DeadlineBlock } from "./deadline.ts"; 15 + import { HiringLagPenaltyBlock } from "./hiringLagPenalty.ts"; 16 + import { MakespanBlock } from "./makespan.ts"; 17 + import { MaxConcurrentResourceBlock } from "./maxConcurrentResource.ts"; 18 + import { OpenUnitPenaltyBlock } from "./openUnitPenalty.ts"; 19 + import { PeakCapBlock } from "./peakCap.ts"; 20 + import { createRegistry, type BlockDef, type BlockRegistry } from "./registry.ts"; 21 + import { ReleaseBlock } from "./release.ts"; 22 + import { SerialSGSBlock } from "./serialSgsSearch.ts"; 23 + import { UnimodalDeviationBlock } from "./unimodalDeviation.ts"; 24 + 25 + /** Versioned registry codes for the shipping blocks. */ 26 + export const STANDARD_CODES = { 27 + maxConcurrentResource: "levelset.MaxConcurrentResource.v1", 28 + peakCap: "levelset.PeakCap.v1", 29 + concurrentUnitsLimit: "levelset.ConcurrentUnitsLimit.v1", 30 + deadline: "levelset.Deadline.v1", 31 + release: "levelset.Release.v1", 32 + concurrentResourceCost: "levelset.ConcurrentResourceCost.v1", 33 + openUnitPenalty: "levelset.OpenUnitPenalty.v1", 34 + hiringLagPenalty: "levelset.HiringLagPenalty.v1", 35 + unimodalDeviation: "levelset.UnimodalDeviation.v1", 36 + makespan: "levelset.Makespan.v1", 37 + serialSGS: "levelset.SerialSGS.v1", 38 + } as const; 39 + 40 + export function standardBlockDefs(): BlockDef[] { 41 + return [ 42 + { 43 + code: STANDARD_CODES.maxConcurrentResource, 44 + family: "constraint", 45 + tier: "estimator", 46 + block: MaxConcurrentResourceBlock as never, 47 + defaultShadow: { resourceUniqueId: 0, max: 1 }, 48 + assumptionTemplate: "at most {max} concurrent tasks on resource {resourceUniqueId}", 49 + }, 50 + { 51 + code: STANDARD_CODES.peakCap, 52 + family: "constraint", 53 + tier: "estimator", 54 + block: PeakCapBlock as never, 55 + defaultShadow: { resourceUniqueId: 0, cap: 1 }, 56 + assumptionTemplate: "peak demand on resource {resourceUniqueId} capped at {cap} units", 57 + }, 58 + { 59 + code: STANDARD_CODES.concurrentUnitsLimit, 60 + family: "constraint", 61 + tier: "estimator", 62 + block: ConcurrentUnitsLimitBlock as never, 63 + defaultShadow: { unitIds: [], max: 1 }, 64 + assumptionTemplate: "at most {max} work units open at once", 65 + }, 66 + { 67 + code: STANDARD_CODES.deadline, 68 + family: "constraint", 69 + tier: "estimator", 70 + block: DeadlineBlock as never, 71 + defaultShadow: { taskUniqueId: 0, latestFinish: 0 }, 72 + assumptionTemplate: "task {taskUniqueId} finishes by day {latestFinish}", 73 + }, 74 + { 75 + code: STANDARD_CODES.release, 76 + family: "constraint", 77 + tier: "estimator", 78 + block: ReleaseBlock as never, 79 + defaultShadow: { taskUniqueId: 0, earliestStart: 0 }, 80 + assumptionTemplate: "task {taskUniqueId} starts no earlier than day {earliestStart}", 81 + }, 82 + { 83 + code: STANDARD_CODES.concurrentResourceCost, 84 + family: "scorer", 85 + tier: "estimator", 86 + block: ConcurrentResourceCostBlock as never, 87 + defaultShadow: { resourceUniqueId: 0, basePrice: 100, growthBase: 2, threshold: 0 }, 88 + }, 89 + { 90 + code: STANDARD_CODES.openUnitPenalty, 91 + family: "scorer", 92 + tier: "estimator", 93 + block: OpenUnitPenaltyBlock as never, 94 + defaultShadow: { unitIds: [], softMax: 0, weight: 1 }, 95 + }, 96 + { 97 + code: STANDARD_CODES.hiringLagPenalty, 98 + family: "scorer", 99 + tier: "estimator", 100 + block: HiringLagPenaltyBlock as never, 101 + defaultShadow: { resourceUniqueId: 0, trainingWeeks: 2, costPerCrewWeek: 1000 }, 102 + }, 103 + { 104 + code: STANDARD_CODES.unimodalDeviation, 105 + family: "scorer", 106 + tier: "engineer", 107 + block: UnimodalDeviationBlock as never, 108 + defaultShadow: { resourceUniqueId: 0, allowSecondPeak: false, jitterTolerance: 1 }, 109 + }, 110 + { 111 + code: STANDARD_CODES.makespan, 112 + family: "scorer", 113 + tier: "sales", 114 + block: MakespanBlock as never, 115 + defaultShadow: {}, 116 + }, 117 + { 118 + code: STANDARD_CODES.serialSGS, 119 + family: "search", 120 + tier: "engineer", 121 + block: SerialSGSBlock as never, 122 + defaultShadow: {}, 123 + }, 124 + ]; 125 + } 126 + 127 + /** A registry pre-loaded with the standard defs; pass extra defs to extend 128 + * (plugins, app-specific blocks, `.v2` revisions). */ 129 + export function standardRegistry(extraDefs?: ReadonlyArray<BlockDef>): BlockRegistry { 130 + return createRegistry([...standardBlockDefs(), ...(extraDefs ?? [])]); 131 + }
+57
test/level-blocks/standard.test.ts
··· 1 + // The standard registry: versioned codes, valid shadows, family coverage. 2 + 3 + import { describe, expect, test } from "bun:test"; 4 + 5 + import { STANDARD_CODES, standardRegistry } from "../../src/level-blocks/standard.ts"; 6 + import type { BlockDef } from "../../src/level-blocks/registry.ts"; 7 + 8 + describe("standardRegistry", () => { 9 + test("registers every standard code under the levelset.<Name>.v<N> convention", () => { 10 + const registry = standardRegistry(); 11 + for (const code of Object.values(STANDARD_CODES)) { 12 + expect(code).toMatch(/^levelset\.[A-Za-z]+\.v\d+$/); 13 + expect(registry.get(code)).toBeDefined(); 14 + } 15 + expect(registry.list()).toHaveLength(Object.values(STANDARD_CODES).length); 16 + }); 17 + 18 + test("covers all four families with exactly one search", () => { 19 + const registry = standardRegistry(); 20 + expect(registry.list({ family: "constraint" }).length).toBeGreaterThanOrEqual(5); 21 + expect(registry.list({ family: "scorer" }).length).toBeGreaterThanOrEqual(5); 22 + expect(registry.list({ family: "search" }).map((d) => d.code)).toEqual([ 23 + STANDARD_CODES.serialSGS, 24 + ]); 25 + }); 26 + 27 + test("defaultShadows apply cleanly through each block", () => { 28 + // register() already parse-validates shadows; also confirm apply() 29 + // accepts them so shadow blocks are runnable, not just parseable. 30 + for (const def of standardRegistry().list()) { 31 + const parsed = def.block.schema.input.parse(def.defaultShadow); 32 + expect(() => (def.block as { apply(input: unknown): unknown }).apply(parsed)).not.toThrow(); 33 + } 34 + }); 35 + 36 + test("extends with extra defs without disturbing the standard set", () => { 37 + const extra: BlockDef = { 38 + code: "myapp.Probe.v1", 39 + family: "collector", 40 + tier: "engineer", 41 + block: { 42 + id: "Probe", 43 + schema: { 44 + input: standardRegistry().get(STANDARD_CODES.makespan)!.block.schema.input, 45 + output: standardRegistry().get(STANDARD_CODES.makespan)!.block.schema.output, 46 + }, 47 + apply: () => ({ tag: "probe" }), 48 + toMiniZinc: () => ({ text: "% probe" }), 49 + doc: { nl: "probe", pseudocode: "probe" }, 50 + } as never, 51 + defaultShadow: {}, 52 + }; 53 + const registry = standardRegistry([extra]); 54 + expect(registry.get("myapp.Probe.v1")).toBeDefined(); 55 + expect(registry.get(STANDARD_CODES.serialSGS)).toBeDefined(); 56 + }); 57 + });