Load, resolve, and plan by hand
The pieces openProject composes are exported for callers that need one of
them alone, for example a manifest with no lockfile yet, or specs resolved
against a provider the registry does not know.
import { buildPlan, loadLock, loadManifest, resolveSpecs } from "pixelkiln"
const loaded = await loadManifest("pixelkiln.manifest.json")
const specs = await resolveSpecs(loaded)
const lock = await loadLock("pixelkiln.lock.json")
const plan = await buildPlan(specs, lock)
loadManifest resolves style inheritance before returning. Callers receive
ordinary, fully validated styles with defaults applied; the hand-authored
extends marker is not present in loaded.manifest. Parent changes therefore
flow into child specs without extra work in library integrations.
Planning performs no provider calls and spends nothing. resolveSpecs uses each
style's provider or the manifest default, then lets that offline adapter's
supports() and estimate() methods determine cost unit and candidate count.
Passing options.provider deliberately overrides that routing for custom
orchestration and tests. A provider may also resolve local files before hashing.
The ComfyUI adapter uses that hook to parse and hash a workflow JSON file without
contacting the server. Provider.resolveInputs does the same for per-asset
provider values: it can keep an absolute file path in the runtime spec while
returning a content-only identity for hashing and provenance. A resolved spec
has the fully inherited style and asset settings plus its effective provider
and deterministic spec hash. Optional
quality settings resolve separately and do not affect provider identity or cost.
Revision settings resolve into spec.revision, including the nested parent
spec, absolute input paths for I/O, content hashes, measured dimensions, mode,
and optional strength. Paths are excluded from the hash; input bytes are not.
plan.groups is the authoritative cost view. Each group contains provider,
costUnit, cost, candidates, and its actionable items. For compatibility,
plan.cost and plan.costUnit retain the single-provider projection; both are
null when a plan spans providers or units.
resumeActions groups current paid-work states into poll, pick, and
fetch. It ignores stale specs and entries missing the remote id needed by
their stage. It also excludes incomplete multi-request checkpoints; those
remain actionable in buildPlan and must return through submit.
For custom orchestration, inspect the dependency gate directly:
import { inspectRevisionReadiness, requireRevisionReady } from "pixelkiln"
const readiness = await inspectRevisionReadiness(spec, lock)
if (readiness && !readiness.ready) console.error(readiness.reason)
// Run again at the boundary where provider work would begin.
await requireRevisionReady(spec, lock)
buildPlan already does the first check and reports an unsafe child as
blocked. submit does the second check automatically. Providers advertise
accepted modes with optional supportsRevision(mode). Omission means no
revision support and makes resolution fail offline.