Skip to content

Distribute the registry as a runnable dependency #157

Description

@lmcorbalan

Objective

Make this repository installable as a git dependency at a tag and expose a
single bin that starts the registry HTTP service, so that canton-dappbooster
can run it in its local stack with the pin recorded in a lockfile.

Rationale

canton-dappbooster already consumes @bootnodedev/canton-wallet-service this
way. It cannot consume this repository, for three independent reasons:

  • registry/ is private: true, lives in a subdirectory, and exposes no bin.
    Neither npm nor pnpm can install a git dependency from a subdirectory.
  • The root postinstall runs scripts/fetch-dep.sh, which wants dpm, a JDK
    17+, git and a 123 MB clone of canton-network/splice. npm runs a
    dependency's postinstall during the consumer's install, so every consumer
    would pay for the whole Daml toolchain in order to start an HTTP service that
    never reads a DAR. On a machine without dpm the install does not slow down,
    it fails.
  • Nothing here is packable. The root manifest has no bin, no files, declares
    none of the service's runtime dependencies, and its type is commonjs while
    the service it would ship is ESM.

The workaround available today is for the consumer to clone this repository at a
tag and run the service out of the working copy. That works, but it puts the pin
in a shell variable rather than a lockfile, and it still needs the Daml
toolchain on the consumer's machine.

Scope

In scope:

  • The root package.json becomes the npm face of the repository: scoped name,
    bin, files, the service's runtime dependencies, and "type": "module".
  • postinstall is deleted. Vendoring the Splice DARs becomes an explicit
    npm run setup, which is the command that already did it.
  • The service is compiled at install time by prepare. Nothing prebuilt is
    committed.
  • A guard that fails when the root and registry/ manifests disagree on a
    dependency.
  • A smoke test that packs, installs and runs the package the way a consumer
    does, with no participant and no Daml toolchain.
  • A CI job that runs both.
  • README.md, RUNBOOK.md, ARCHITECTURE.md, CLAUDE.md and SPEC.md
    updated for all of the above.
  • The npm version goes to 0.2.0, and v0.2.0 is cut after merge so that the
    README's pin resolves.

Out of scope:

  • Publishing to the public npm registry. The package is consumed from git, the
    same way the wallet service is.
  • A container image.
  • Any change to the service's routes, configuration, defaults or behaviour.
  • Any change to the Daml packages, the DAR, or the release artifact flow.
    daml/canton-token-forge/daml.yaml stays at 0.0.1.
  • Converting the repository to npm workspaces. A consumer installing a git
    dependency gets that package's own dependencies and nothing else, so
    workspaces buy nothing here.

Architecture & technical considerations

The model is @bootnodedev/canton-wallet-service: public on GitHub, absent from
the public npm registry, consumed as a git dependency so the tag lands in the
consumer's lockfile. Its root manifest is the service (bin at
dist/server.js, files: ["dist", ".env.example"], prepare runs tsc). The
one difference here is that our service is not at the root, so the root manifest
ships a subdirectory's build output.

Five facts, each established by running it against the tree, shape the work:

  1. npm packs no nested manifest and no node_modules. registry/package.json
    does not travel in the tarball. So the four runtime dependencies must be
    declared in the root manifest, because nothing else can be present for the
    bin's imports to resolve against, and the ESM marker has to be on the root
    manifest too.
  2. A nested .gitignore outranks the root files allowlist for paths inside
    it; the root .gitignore does not.
    registry/.gitignore currently ignores
    dist, which silently empties the package: files: ["registry/dist"] ships
    zero files, and the consumer installs a bin pointing at nothing. Moving that
    one rule up to the root .gitignore fixes it and leaves git's behaviour
    identical.
  3. express-openapi-validator reads its spec lazily. A spec file left out of
    the tarball does not fail the boot: the service starts and answers
    GET /healthz 200, and only fails the first API request with
    500 openapi.validator: spec could not be read. Any check that the specs
    shipped has to inspect the archive or issue a real API request.
  4. An unreachable participant does not stop the boot. Only two participant
    answers are fatal, both attributable to our own configuration; everything else
    warns and continues, and /healthz never touches the ledger. That is what
    makes an install smoke test possible with no participant running.
  5. tsc preserves a shebang, so the bin target needs no wrapper file, and
    npm sets the exec bit when it links it.

The main risk this introduces: the root and registry/ manifests will name
overlapping packages, and drift between them means a consumer resolving a
different express than the suites ran against. Mitigated by a guard that fails
CI rather than a consumer's install.

Dependencies

  • Blocks: canton-dappbooster running this registry in its local stack.
  • Blocked by: nothing.
  • This repository is private, so a consumer's install needs git credentials for
    github.com. That is a documented precondition, not something this epic can
    remove.

Issue breakdown

Each lands as its own pull request against a shared aggregate branch; one pull
request from that branch to main closes this epic.

Acceptance criteria

  • A consumer pins this repository at a tag in dependencies and gets a
    working install with neither dpm nor a JDK on PATH
  • pnpm exec canton-token-forge-registry starts the service, configured
    entirely from the environment
  • The install needs no Daml dependency fetch, and nothing prebuilt is
    committed
  • A single command in this repository proves the published package installs
    and serves, and CI runs it on every change that could break it
  • The root and registry/ dependency lists cannot drift without failing a
    check
  • The README documents the consumer install, the run, the required
    environment, and the git-credentials precondition
  • Every document that describes the install lifecycle matches what it now
    does
  • v0.2.0 is cut after merge and the pin the README names resolves

Metadata

Metadata

Assignees

Labels

epicLarge body of work broken into smaller issuespriority: highMust be addressed in current sprint

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions