Bindings

Bare names and the trust-evaluation moment. Project bindings, global bindings, and how resolution works.

Esker datasets are addressed by <owner>/<name>@<version>. Two different owners can publish the same <name> — the platform never picks a winner. So how does your code stay readable when every dataset has an owner prefix?

The same way every package manager solves the same problem: bind once, then live in bare-name space.

The moving parts

Three files. All TOML. Same shape as package.json + package-lock.json or pyproject.toml + uv.lock.

Project bindings — pyproject.toml:

[tool.esker]
owner = "statcan"   # default publish owner

[tool.esker.datasets]
"ca.corporations.registry" = "statcan/ca.corporations.registry"
"us.sec.companies"          = "sec-foundation/us.sec.companies"

Lockfile — esker.lock (committed):

# generated; do not edit

[[dataset]]
name           = "ca.corporations.registry"
owner          = "statcan"
version        = "1.0.0"
schema_version = "1.0.0"
content_hash   = "sha256:..."
lineage_hash   = "sha256:..."
resolved_at    = "2026-04-26T12:00:00+00:00"

Global bindings — ~/.esker/config.toml (off-project fallback):

[datasets]
"ca.corporations.registry" = "statcan/ca.corporations.registry"

The resolution rule

When you write esker.get("ca.corporations.registry") or esker pull ca.corporations.registry:

  1. If the input is a full ref (<owner>/<name>[@<version>]), use it directly.
  2. Otherwise look up the bare name in project [tool.esker.datasets].
  3. Otherwise look up in global [datasets].
  4. Otherwise: no binding for <name> · run 'esker add <owner>/<name>' or use a full ref.

When a project binding resolves, the lockfile (if present) pins the version. Bare names never auto-resolve when a project has no binding, even if only one publisher exists. Strictness is a feature: behavior should not change the day a second publisher arrives.

Pinned bindings

A binding can include @<version> to pin to an exact version:

[tool.esker.datasets]
# tracks latest — esker.lock supplies the pin
"us.treasury.yields" = "archie/us.treasury.yields"

# exact pin — durable across `esker upgrade`
"us.treasury.yields" = "archie/us.treasury.yields@1.0.0"

A pinned binding short-circuits the lockfile entirely — pyproject.toml is durable intent. esker upgrade refuses to bump pinned bindings.

The workflow

Bind a dependency. The trust-evaluation moment.

$ esker search ca.corporations.registry
  statcan/ca.corporations.registry         12,847 records · 4.2k pulls/30d · verified
  community/ca.corporations.registry          8,231 records · 142 pulls/30d

$ esker add statcan/ca.corporations.registry
  ca.corporations.registry → statcan/ca.corporations.registry@1.0.0
  pyproject.toml · esker.lock

Reconcile the cache to the lockfile (mirror of bun install or uv sync):

$ esker sync
  ca.corporations.registry · statcan/ca.corporations.registry@1.0.0

Re-resolve to the latest hub version, rewriting the lockfile:

$ esker upgrade ca.corporations.registry
  ca.corporations.registry · 1.0.0 → 1.1.0

Drop the binding entirely:

$ esker remove ca.corporations.registry
  removed ca.corporations.registry

Project root walk

Project bindings are scoped to the nearest pyproject.toml. The resolver walks up from the current working directory until it finds one. Inside a workspace this picks up the inner project's bindings, not the workspace root's.

If there is no pyproject.toml in the cwd or its parents, esker add errors:

$ esker add statcan/ca.corporations.registry
  no pyproject.toml in cwd or parents · use --global to bind

Surgical TOML edits

add and remove use line-pattern regex on the binding lines, not full TOML round-tripping. They preserve user formatting, comments, blank lines, and key ordering outside the touched block.

The cost: a multi-line value or unusual spacing could break the regex. Standard one-line bindings work fine.

Why

Three reasons.

  1. The owner choice is one explicit moment, not a thousand. esker add is the only place you have to think about which statcan/ca.corporations.registry you're trusting. Everything downstream — code, CLI, notebooks — uses the bare name.
  2. Reproducibility is a checkbox, not a discipline. esker.lock pins the exact content hash. Clean clone + esker sync reproduces your cache deterministically.
  3. The platform stays infrastructure. Esker doesn't decide which ca.corporations.registry is "the canonical one." You do, once, in your project. The disambiguation page at esker.so/<name> ranks publishers by mechanical signal (verification, pull volume, recency) — never by editorial judgment.

See also