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:
- 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.
- 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.
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.
- 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.
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
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-dappboostercan run it in its local stack with the pin recorded in a lockfile.
Rationale
canton-dappboosteralready consumes@bootnodedev/canton-wallet-servicethisway. It cannot consume this repository, for three independent reasons:
registry/isprivate: true, lives in a subdirectory, and exposes no bin.Neither npm nor pnpm can install a git dependency from a subdirectory.
postinstallrunsscripts/fetch-dep.sh, which wantsdpm, a JDK17+,
gitand a 123 MB clone ofcanton-network/splice. npm runs adependency's
postinstallduring the consumer's install, so every consumerwould pay for the whole Daml toolchain in order to start an HTTP service that
never reads a DAR. On a machine without
dpmthe install does not slow down,it fails.
bin, nofiles, declaresnone of the service's runtime dependencies, and its
typeiscommonjswhilethe 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:
package.jsonbecomes the npm face of the repository: scoped name,bin,files, the service's runtime dependencies, and"type": "module".postinstallis deleted. Vendoring the Splice DARs becomes an explicitnpm run setup, which is the command that already did it.prepare. Nothing prebuilt iscommitted.
registry/manifests disagree on adependency.
does, with no participant and no Daml toolchain.
README.md,RUNBOOK.md,ARCHITECTURE.md,CLAUDE.mdandSPEC.mdupdated for all of the above.
0.2.0, andv0.2.0is cut after merge so that theREADME's pin resolves.
Out of scope:
same way the wallet service is.
daml/canton-token-forge/daml.yamlstays at0.0.1.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 fromthe public npm registry, consumed as a git dependency so the tag lands in the
consumer's lockfile. Its root manifest is the service (
binatdist/server.js,files: ["dist", ".env.example"],preparerunstsc). Theone 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:
node_modules.registry/package.jsondoes 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.
.gitignoreoutranks the rootfilesallowlist for paths insideit; the root
.gitignoredoes not.registry/.gitignorecurrently ignoresdist, which silently empties the package:files: ["registry/dist"]shipszero files, and the consumer installs a bin pointing at nothing. Moving that
one rule up to the root
.gitignorefixes it and leaves git's behaviouridentical.
express-openapi-validatorreads its spec lazily. A spec file left out ofthe tarball does not fail the boot: the service starts and answers
GET /healthz200, and only fails the first API request with500 openapi.validator: spec could not be read. Any check that the specsshipped has to inspect the archive or issue a real API request.
answers are fatal, both attributable to our own configuration; everything else
warns and continues, and
/healthznever touches the ledger. That is whatmakes an install smoke test possible with no participant running.
tscpreserves a shebang, so the bin target needs no wrapper file, andnpm sets the exec bit when it links it.
The main risk this introduces: the root and
registry/manifests will nameoverlapping 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
canton-dappboosterrunning this registry in its local stack.github.com. That is a documented precondition, not something this epic canremove.
Issue breakdown
Each lands as its own pull request against a shared aggregate branch; one pull
request from that branch to
maincloses this epic.Acceptance criteria
dependenciesand gets aworking install with neither
dpmnor a JDK onPATHpnpm exec canton-token-forge-registrystarts the service, configuredentirely from the environment
committed
and serves, and CI runs it on every change that could break it
registry/dependency lists cannot drift without failing acheck
environment, and the git-credentials precondition
does
v0.2.0is cut after merge and the pin the README names resolves