feat(analyzer): Daml DAR analyzer — core, CLI, REST, and Web UI - #295
Draft
zheli wants to merge 108 commits into
Draft
feat(analyzer): Daml DAR analyzer — core, CLI, REST, and Web UI#295zheli wants to merge 108 commits into
zheli wants to merge 108 commits into
Conversation
The help text no longer references cache/instance paths, but the format call still passed those values and failed govet printf. Co-authored-by: Cursor <cursoragent@cursor.com>
Modified the labels for wallet endpoints in the orderedEndpointKeys function to include the username for each wallet type, enhancing clarity for users. This change improves the user interface by providing more descriptive labels for the Wallet options.
The down test computed the non-devkit container count with
`grep -cv ... || echo "0"`. On empty/no-match input, `grep -c`
already prints "0" but also exits non-zero, so the `|| echo "0"`
fallback fired and produced a two-line value ("0\n0"). That broke the
subsequent `[ "$BEFORE" -eq "$AFTER" ]` integer comparison
("[: 0\n0: integer expected"), making the test fail with
"down failed or containers remain" even though localnet down
succeeded.
Drop the redundant fallback so the count is a clean integer.
The E2E job runs on a persistent self-hosted runner. `docker compose down --volumes` only removes volumes Compose itself created; volumes left by an earlier run are re-adopted as external on the next `up` (Compose warns "already exists but was not created by Docker Compose") and are NOT removed by `down --volumes`. This stranded canton-e2e-test-default_postgres and _domain-upgrade-dump across runs, tripping M1-CLN-001's "volumes remain after clean" check even though `canton-devkit clean` ran correctly. The failure was an environment/harness issue, not a product bug. Explicitly remove the per-project volumes by name prefix in both the pre-run "Clean stale state" and the "Force cleanup on failure" steps so each run starts from a clean slate regardless of how prior volumes were created.
Updated AGENTS.md to include a new section on best practices for creating temporary files and directories. Emphasized the importance of using the current working directory or repository root, suggested relative paths, and outlined cleanup procedures. Noted exceptions for using system-level directories like /tmp when necessary.
Add docs/changes-from-proposal.md, a maintained changelog of every deliberate deviation between the original Development Fund proposal (docs/original-devkit-proposal.md) and the shipped implementation. Seeded with 24 entries covering all deviations audited from the current CLI surface: instance name addressing, --format vs --json, command aliases, new lifecycle/inspection/token subcommands, connection flag conventions, telemetry, and up-time flag additions. Also adds a load-bearing 'Proposal deviation tracking' rule to AGENTS.md (plus PR checklist item #7) requiring contributors to update the changes file in the same PR whenever command syntax, flags, aliases, defaults, or user-facing behaviour diverges from the proposal. The rule lives in AGENTS.md rather than a .claude/skills/ skill so it fires on every agent session, not only when an agent judges the task matches.
Group all deviations by the subcommand they affect instead of a flat list. Add a top-level framing paragraph stating that every deviation is intentional (UX, performance/resource efficiency, security, correctness, or CLI \u2194 Web UI parity) rather than a mistake. Structural changes: - Add 'Cross-cutting conventions' section for name addressing, --format, and aliases (span multiple commands so not tied to one subcommand) - Group all three 'localnet up' flag additions under a single section with subsections - Merge contracts/tx entries into one section with subsections - Merge dar entries into one section with subsections - Merge all five token entries into one section with subsections - Rebuild table of contents to match new structure No content removed; all 'Proposal said / Shipped / Why' text preserved and lightly improved to reinforce intentionality.
Comment out the DPM component OCI publish steps (Install DPM CLI, Lay out component dirs, Log in to GHCR, Validate manifest, Publish) and their associated env vars (GHCR_NAMESPACE, DPM_VERSION, DPM_LINUX_SHA256). The OCI publish now lives in homebrew-canton-devkit's publish-oci.yml workflow, which triggers on GitHub Release creation and publishes to the public namespace ghcr.io/bitdynamics-ab/homebrew-canton-devkit:<ver>. The commented-out steps are preserved with a TODO for re-enabling once we have a public OCI registry we can push to directly from this repo. Also updates docs (getting-started.md, packaging.md, README.md) to reference the new public GHCR namespace.
This reverts commit 1dad920.
The release workflow has no docker build/push. Everything published to ghcr.io/bitdynamics-ab/canton-devkit:<tag> goes through 'dpm publish component'. This was leftover wording from before the DPM-component publish existed.
…rkflow Describes the GitHub Actions workflow to add at .github/workflows/verify-public-oci.yml. Verifies that the public canton-devkit DPM component OCI artifact is: - Anonymously pullable from GHCR (package is Public) - Multi-arch in OCI index metadata (linux/amd64, darwin/arm64, windows/amd64) - Installable + runnable on linux/amd64 via 'dpm install package oci://...' Resolves the sdk-version open question: 'dpm install package' accepts the OCI ref as a positional argument, requiring no project file or sdk-version.
Weekly (Mon 05:00 UTC) + manually-dispatchable workflow that proves the published ghcr.io/bitdynamics-ab/canton-devkit DPM component is: 1. Anonymously pullable from GHCR (the package is genuinely Public). 2. Multi-arch in metadata — the OCI index lists linux/amd64, darwin/arm64, and windows/amd64. 3. Installable + runnable on linux/amd64 via 'dpm install package oci://...' and 'dpm localnet --help'. The check uses only raw curl (v2 API with a self-fetched public pull token) and the sha256-pinned DPM CLI — no Docker credentials, no Docker images, no marketplace actions beyond curl/jq on the runner. Also adds a '# Keep in sync with verify-public-oci.yml' cross-reference comment in release.yml next to DPM_VERSION / DPM_LINUX_SHA256 so both files are bumped together.
$GITHUB_PATH additions only take effect in subsequent steps, not in the same step where the echo is done. Bare 'dpm --version' therefore failed with exit 127. Use "$bindir/dpm" --version (matching release.yml:401).
Problem 1: 'dpm install package' requires a daml.yaml with a components: entry in the current directory — there is no argument form. The step was passing the OCI ref as a positional arg, which dpm does not support. Problem 2: 'dpm install package' requires a strict semver OCI tag; symbolic tags like 'latest' are rejected with 'invalid semantic version'. The resolved version is extracted from the manifest's org.opencontainers.image.version annotation (written by dpm publish) and stored as INSTALL_VERSION for use in the install step. Fix: after the anonymous fetch step, extract INSTALL_VERSION from manifest.json. The smoke test creates a minimal daml.yaml (no sdk-version to avoid the 'opt-in components + SDK bundle' conflict) in a RUNNER_TEMP workdir, then runs 'dpm install package' and 'dpm localnet --help' from that directory. Verified locally against latest (resolved to 0.10.1).
Bumps [undici](https://github.com/nodejs/undici) from 7.27.0 to 7.28.0. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](nodejs/undici@v7.27.0...v7.28.0) --- updated-dependencies: - dependency-name: undici dependency-version: 7.28.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Rename for clarity — the workflow name now describes what it tests (canton-devkit end-to-end functions / Milestone 1 lifecycle) rather than just 'E2E'. Updates the display name to match.
Updates display name to 'E2E: DPM Installation', job name to e2e-test-dpm-installation, and the cross-reference comment in release.yml. No logic changes.
The App config panel and JWT generator rendered tokens as <redacted>, so copy-pasted env/json/yaml config and generated JWTs were unusable against the running ledger. LocalNet is loopback-only and signs with a shared dev secret (the dev-secret warning already renders on the JWT panel), so surface the raw token directly: - api.ts: app-config fetchers request ?include_jwt=true - DeveloperSetup: JWT panel issues with include_jwt=true on mount and shows the token immediately; drop the reveal/hide toggle in favor of a Copy-only button Backend and CLI keep their redacted-by-default behavior; only the LocalNet Web UI opts into raw tokens.
The unquoted 'name: E2E: ...' value was parsed as a nested mapping (colon-space), making both e2e workflows invalid YAML so GitHub Actions never ran them. Quote the value to fix parsing.
- Reconcile platform-support claim in faq.md with the tested matrix (macOS arm64, Linux amd64, Windows amd64). - Drop the "Homebrew not yet published" hedge in getting-started; point to docs/homebrew.md (tap + formula automation already ship). - Rewrite the limitations.md observability section: the host-level shared stack has shipped (observability.md is authoritative); the remaining limitation is the transitional per-instance dual stack. - Remove brittle "proposal line 188" citations. - Fix a broken link to a nonexistent docs/issues/*.md in the M1 e2e transcript. - Align install-snippet version placeholders in packaging.md. - Add observability.md to the README docs index. - Move two stale internal docs out of the tree (content preserved in Linear BIT-234 and BIT-235): design/localnet-token-workspace.md (shipped; now covered by docs/tokens.md) and ux-improvement-followup.md (residual --name→positional TODO). - Remove obsolete .claude/launch.json.
Remove documents that only made sense inside the development-fund process: the original proposal, the proposal-deviation log, the reviewer kit, the internal telemetry design proposal, and the per-milestone e2e test scripts. Reframe the remaining docs in neutral OSS voice: no milestone/acceptance framing, no internal reviewer process, working links only. Replace AGENTS.md with CONTRIBUTING.md: same engineering rules (build, test, lint, testing requirements, the CLI/Web-UI parity convention, commit and PR guidance) minus the internal process framing. Drop CLAUDE.md and local tooling entries from .gitignore.
Remove comments that restate the code, historical narration, and internal process references; compress verbose rationale to its load- bearing core; delete provably dead unexported code; apply mechanical simplifications (redundant else-after-return, unneeded conversions, single-use trivial helpers) where equivalence is certain. Deliberately unchanged: all exported identifiers and signatures, error strings, exit codes, CLI flags and help text, JSON field names, HTTP routes, log formats, and generated files. Genuine why/invariant/ security/concurrency comments stay. Full go test, golangci-lint, tsc, and vitest suites pass identically.
New website/ directory with a Starlight site built from the repo docs: landing page with quick start, guides (lifecycle, DAR, explorer, observability, tokens), and reference (versions, packaging, telemetry, limitations, troubleshooting). Builds with npm run build (16 pages). Add a GitHub Pages deploy workflow (SHA-pinned actions) that publishes the site on pushes to main touching website/ or docs/.
Cleanup helpers unconditionally ran `docker compose down --volumes` after `canton-devkit remove`, and that fallback left detached volumes behind (compose down only removes volumes it created; the restore path re-creates the postgres volume out of band via `docker run -v`, so it is adopted as external and skipped) -- the leak that trips M1-RMV-001. - Always use `canton-devkit remove` first; only fall back to docker when remove fails or resources actually remain (e2e_cleanup_instance + e2e_instance_resources_remain). - When the docker fallback does run, remove everything including detached volumes via a name-prefix `docker volume rm -f` sweep (e2e_force_docker_cleanup). - Rewire lib.sh cleanup, m1-snp-001, m1-up-002, and m1-rmv-001 fallbacks.
* feat(ui): show build commit id in side nav footer Replace the non-actionable "loopback only" / "schema v1" footer text with the short git commit the UI bundle was built from, so screenshots reveal which UI commit a user is on. Injected via a Vite `define` constant sourced from `git rev-parse --short HEAD` at build time, falling back to "dev" outside a git checkout. * feat(ui): keep schema version in footer alongside commit id The schema version still guards the stale-tab-after-upgrade case, so show it in the side nav footer next to the UI build commit id. * feat(ui): label footer commit as "UI version" * feat(ui): link footer UI version to the GitHub commit Render the build commit id as a link to github.com/bitdynamics-ab/canton-devkit/commit/<sha>. The "dev" fallback (no git checkout / Vitest) stays plain text since there's no commit to point at.
The per-test-job e2e pinned actions/download-artifact to a corrupted SHA (65a9edc12816...) that no longer resolves, so every M1 test job failed at 'Unable to resolve action' and the suite never ran (red on main). Repin to the real v4.1.7 commit (65a9edc58814...).
…nce sidecars (#264) Every observability-enabled LocalNet also spun up its own Prometheus+Grafana overlay on top of the host-shared stack, so N instances ran N+1 of each — pure duplication that wastes ~600 MiB per environment and pressures a memory-capped Docker host. `up --observability-mode auto|shared|per-instance` (default auto) now prefers the shared stack and skips the per-instance overlay when it is reachable; per-instance remains the platform-independent fallback. The mode is persisted so a re-up preserves it. When the overlay is skipped the metrics/dashboard links resolve to the shared Grafana (filtered to the instance) rather than a dead, allocated-but-unbound per-instance port. Making shared the default and dropping the overlay entirely waits on validating the shared stack's host.docker.internal scrape path on native Linux; until then auto covers Docker Desktop and per-instance is the escape hatch.
…265) * feat(observability): --observability-mode to skip redundant per-instance sidecars Every observability-enabled LocalNet also spun up its own Prometheus+Grafana overlay on top of the host-shared stack, so N instances ran N+1 of each — pure duplication that wastes ~600 MiB per environment and pressures a memory-capped Docker host. `up --observability-mode auto|shared|per-instance` (default auto) now prefers the shared stack and skips the per-instance overlay when it is reachable; per-instance remains the platform-independent fallback. The mode is persisted so a re-up preserves it. When the overlay is skipped the metrics/dashboard links resolve to the shared Grafana (filtered to the instance) rather than a dead, allocated-but-unbound per-instance port. Making shared the default and dropping the overlay entirely waits on validating the shared stack's host.docker.internal scrape path on native Linux; until then auto covers Docker Desktop and per-instance is the escape hatch. * feat(observability): expose --observability-mode in the Web UI create flow Phase 1 added the CLI flag, but the Web UI create path always used the auto default with no way to choose — a CLI-only feature. Wire the same control for CLI ↔ Web UI parity: the create request carries observability_mode (validated server-side with the same rule as the CLI), threaded into UpOptions, and the create modal shows a "Sidecar stack" picker (auto | shared | per-instance) when an observability profile is selected. * feat(observability): health-probe auto fallback, shared-stack status, Linux e2e Three robustness/coherence additions on top of the shared-only mode: - auto now health-probes the shared Prometheus (/-/healthy) after ensuring the stack, so an instance isn't bound to a stack that is up but not serving — it falls back to the per-instance overlay instead. - observability status reports a "Shared stack: registered" line (+ shared in the JSON) so a shared-only instance reads coherently rather than "off". - scripts/e2e-observability.sh + a CI job validate the shared-only scrape path (incl. host.docker.internal reachability) on the self-hosted Linux runner — the gate for making shared the default. * docs(observability): document mode persistence, status source, Web UI picker, auto health-probe * fix(e2e): run localnet up via the binary, not the cli() function, under timeout --------- Co-authored-by: Zhe Li <linuxcity.jn@gmail.com>
RunUp was refactored to take a Progress (RunUp(ctx, prog, opts)), but the build-tagged integration_test.go still passed (ctx, os.Stdout, os.Stderr, opts). Normal `go test` doesn't compile integration-tagged files, so it rotted undetected — the Integration workflow has been red on main with 'too many arguments in call to RunUp'. Wrap stdout/stderr in NewTextProgress, matching the CLI's RunUp call.
* test(e2e): add bats-core dpm localnet suite (DPM-DAR-001) Reimplement the `dpm localnet` end-to-end suite on bats-core instead of a hand-rolled shell harness. bats-core and its helper libraries are vendored as pinned git submodules under e2e-tests/: e2e-tests/bats bats-core v1.13.0 e2e-tests/test_helper/bats-support v0.3.0 e2e-tests/test_helper/bats-assert v2.2.4 DevKit-specific helpers (component assembly + Daml project scaffolding) live in e2e-tests/test_helper/dpm.bash; the generic pass/fail/skip and summary machinery is now provided by bats itself. DPM-DAR-001 (e2e-tests/dpm-dar-001.bats) is the first case: it builds a local file-based component from the freshly built binary, scaffolds a Daml project, and asserts `dpm localnet dar build-upload --build-only` succeeds without the issue #230 "file exists" signature. --build-only means no LocalNet is required. Run locally with `make e2e-dpm` (initializes submodules on demand, skips gracefully when dpm is absent). CI recreates the hermetic, no-artifact single-runner workflow, now invoking bats via a composite action with submodules: recursive checkout. Note: milestone1 (scripts/e2e-milestone1.sh) remains on the old harness and will migrate to bats in a follow-up. * docs(e2e): document the bats-core dpm localnet e2e suite Add docs/e2e-testing.md covering both E2E layers (the new bats-core `dpm localnet` suite under e2e-tests/ and the Milestone 1 LocalNet lifecycle suite): layout, the pinned bats submodules, running locally via `make e2e-dpm`, writing new .bats tests with dpm.bash helpers, the CI workflow, and how to bump pinned versions. Publish it to the docs site: add a docs-map.mjs entry (reference/ e2e-testing) and a Reference sidebar link in astro.config.mjs.
… of band (#273) * fix(localnet): restore requires a prior up; never create a volume out of band RunRestore loaded a pg_dumpall stream into <project>_postgres via a throwaway `docker run -v <vol>:...`. When the target instance had never been `up`, that `docker run` CREATED the volume out of band, with no `com.docker.compose.*` labels. A later `up` adopts it (the "volume ... already exists but was not created by Docker Compose" warning), and `docker compose down --volumes` (used by `down` and `remove --force`) refuses to delete a volume Compose did not create — so the volume is stranded. This is the root cause behind the M1-RMV-001 e2e failure ("FAIL step 3b: volumes remain after remove"). Require the target instance to already be registered (it was `up`, so Compose owns the volume) before restoring; refuse otherwise and tell the user to run `localnet up <name>` first. Applies to cross-name restore too. This keeps the "never create a volume outside Compose" invariant. The precondition is enforced via the registry (a proxy for "the Compose-owned volume exists"). A stricter Docker volume-ownership label check is deferred and documented in docs/limitations.md. Tests: seed a stopped instance in the round-trip, cross-name, and content-SHA restore tests; add TestRestore_RefusesUnknownInstance. * test(e2e): M1-SNP-001 uses `down` (not `remove`) before restore Restore now loads into the instance's EXISTING Compose-owned Postgres volume and refuses to create one out of band. The snapshot/restore test tore the instance down with `remove` (which reclaims the volume), then restored — which the new contract correctly rejects with "instance not found — run localnet up first". Switch the teardown to `down`, which preserves the volume, so restore has a Compose-owned volume to load into. * test(e2e): M1-UP-003 precleans e2e-version-test before up The test brings up a fresh `e2e-version-test` but had no precondition cleanup, so a leftover instance from a prior/aborted run caused step 1 to abort with "instance already running". Add a best-effort `e2e_cleanup_instance` before `up`, matching the self-defending pattern used by other lifecycle tests. Cleanup lives in the test's precondition (not teardown) because M1-LST-001 depends on e2e-version-test staying up.
Reduce required fields from seven to three so reporters can file bugs without filling separate expected/actual, surface, and OS dropdowns.
Splice's nginx routes by Host header. The bare host URL DevKit
advertises (http://localhost:<UI_PORT>) matches no *.localhost vhost, so
nginx falls through to the first server block on the port:
- app-provider (52343): first block is ans.localhost -> served the
Amulet Name Service instead of the wallet.
- sv (52344): catch-all `server_name localhost _` served a
non-existent static dir (/usr/share/nginx/sv-html) -> HTTP 404.
app-user already worked because its wallet block listed
`server_name localhost wallet.localhost`.
The upstream .conf files live in the content-hash-verified Splice cache
(shared across all instances), so editing them in place risks a hash
mismatch and is not per-instance safe. Instead, ship DevKit-owned copies
as embedded assets and bind-mount them over the upstream templates via a
per-instance compose overlay (WriteNginxVhostOverlay), mirroring the
existing loopback-ports/container-rename overlay pattern.
The DevKit copies add `localhost` to each wallet server block and drop
`localhost` from the sv catch-all. nginx.conf and the includes/ dir are
unchanged upstream, so the overlay keeps those pointed at the cache.
Verified end-to-end against localnet-2: all three bare wallet URLs now
return HTTP 200 and serve the wallet bundle, with ans.localhost,
scan.localhost, and sv.localhost vhosts still routing correctly.
…n existing assets (#240) * Update artifact references in index.html and do not fallback to index.html for non existing assets * Update comments * Update comment
* docs: list full DPM SDK packages in daml.yaml examples DPM projects need the Canton 3.5.2 SDK components alongside the DevKit OCI package; the install examples previously omitted most of them. * chore: ignore workspace files and keep tmp/ ignored Exclude local *.code-workspace files from git and keep tmp/ listed with the other build/scratch ignores. * docs: keep DPM SDK version as a generic placeholder Use <your-sdk-version> in daml.yaml examples instead of a pinned release so the install docs stay version-agnostic. * docs: keep concrete 3.5.2 versions in README daml.yaml example Leave the README install example pinned so readers can copy it as-is; getting-started and packaging keep the generic placeholder. * ci: skip e2e and integration workflows on doc-only PRs Ignore docs/, website/, and Markdown paths on pull_request so labeled or synchronized doc-only changes do not queue the heavy runners.
…280) * feat(localnet): instance-scoped nginx vhosts for every Splice UI/API Serve each Splice UI/API behind an instance-scoped virtual host of the form <service>.<instance>.localhost (e.g. wallet.localnet-2.localhost) instead of the flat *.localhost names, so URLs stay unambiguous across concurrently running localnets. - assets/nginx/{app-provider,app-user,sv}.conf: server_name is now a ${VHOST_*} placeholder (wallet, ans, scan, sv, json-ledger-api, grpc-ledger-api); the flat Splice names are dropped. The deprecated canton.localhost block is left flat. - overlay.go: add instanceVHost() + VHostService* consts; thread the instance name into WriteNginxVhostOverlay and inject the per-instance VHOST_* values as nginx container env (envsubst expands them at boot). - up.go / status.go: advertise wallet UIs at wallet.<instance>.localhost in the welcome screen and status endpoints. - env.go: instance-scope CANTON_SCAN_UI_URL and emit per-role CANTON_<ROLE>_{JSON,GRPC}_LEDGER_API_URL plus unqualified CANTON_{JSON,GRPC}_LEDGER_API_URL aliases (app-provider), each behind the matching ledger-api vhost. - token/registry_url.go + registry/client.go + registry/doc.go + canton integration test: thread the instance-scoped scan Host header through DevKit's own scan-registry client. - ui_reachability.go: dial loopback (multi-label *.localhost does not resolve via the OS/Go resolver on macOS) but carry the wallet vhost as the Host header so the probe validates the real route. Note the resolution caveat documented throughout: *.localhost resolves to 127.0.0.1 in browsers, curl, and Go, but not in JVM/Node/Python resolvers, which must send an explicit Host header (HTTP) / :authority pseudo-header (gRPC) or add an /etc/hosts entry. * feat(localnet): role-scoped nginx vhosts for wallet and ledger APIs Serve role-specific Splice services at role-scoped instance vhosts of the form <service>.<role>.<instance>.localhost (wallet, json-ledger-api, grpc-ledger-api, ans) so per-role UIs/APIs are unambiguous. Single-per- instance services (scan, sv) keep the shorter <service>.<instance> shape. The longer role-scoped names overflow nginx's default 64-byte server-name hash bucket, so add a DevKit-owned http-context tuning snippet (00-devkit-tuning.conf) that bumps server_names_hash_bucket_size to 128 and mount it straight into conf.d; without it nginx aborts on boot with "could not build server_names_hash".
- Rename the test to make explicit it only exercises the build step of `dpm localnet dar build-upload` (--build-only); no LocalNet or upload RPC is involved. - e2e-dpm-test action: tee bats TAP output and render a markdown pass/fail table to $GITHUB_STEP_SUMMARY so results are visible in the GitHub Actions run summary without opening logs.
…istory, allocations (DvP), batching (#277) * feat(token): bundle Token Standard V2 DARs, interface consts, shared types Foundation for CIP-0112 (Token Standard V2): auto-bundle the allocation-v2, transfer-events-v2, and util-token-standard-wallet DARs alongside the test-token DAR, add the V2 interface/choice name constants, and define the schema-pinned API types shared by the CLI --json output and the Web UI REST/SSE payloads so the two surfaces cannot drift. * feat(token): identity switcher, EventLog history, allocations/DvP, batching Four CIP-0112 capabilities on both the CLI and the Web UI Tokens screen, over one shared orchestration layer: - identity/act-as picker: operate as app-user / app-provider / sv, with the role threaded through every token call. - EventLog transaction history: per-instrument activity reconstructed from the V2 EventLog interface, newest-first. - allocations / DvP: allocate, list, settle, withdraw, cancel via the V2 AllocationFactory. - opt-in atomic transfer+accept batching via BatchingUtilityV2. * fix(token): live-validated V2 correctness and Tokens-page UX Fixes found by exercising create -> mint -> transfer -> allocate on a live LocalNet, plus Tokens-screen UX. Each lands on both the CLI and the Web UI: - authorize V2 ops: grant read-any + per-issuer act-as, resolve issuer aliases on create, onboard freshly-allocated parties to the synchronizer. - self-custodial receiver account so V2 mint accept works; reject self-mint (issuer == receiver) with an actionable error. - correct the allocation wire shapes to V2 (drop expectedAdmin; SettlementInfo and TransferLegSide v1 -> v2). - list created-but-unminted instruments (discovery was holdings-only). - drop phantom bare-alias parties from the readable set and matrix. - Holdings matrix filter-by-token; copy party ids; paginate the Activity feed newest-first. - --atomic batching: wire shapes corrected, but closed as experimental (ExecuteBatch does not rebind the accept leg to the transfer leg's intra-batch instruction) -- it fails with a clear error; sequential transfer+accept is the supported default. * chore(token): trim comments and drop internal references Open-source hygiene pass over the token subsystem: cut running commentary and code-restating comments (~1000 fewer comment lines) while keeping the load-bearing wire-shape and Daml-quirk rationale, and remove internal ticket tags and notes. Comment-only -- no behavior change. * fix(token): demo mints supply to holder, not the issuer The V2 demo minted the initial supply to the issuer party, which the self-mint guard added in this branch rejects (issuer == receiver) — so token demo failed every time on a tokens-v2 instance. Mint the supply to a distinct holder party instead, which both satisfies the guard and lands a transferable balance in one step, dropping the redundant issuer->holder faucet leg. The holder is now intrinsic to the V2 demo, so --seed-holder is removed. * fix(token): auto-resolve ledger endpoint for mint/burn/transfer/accept mint and burn only took the live path when --endpoint was passed explicitly; otherwise they fell through to ErrUnsupportedOnInstrument, which also meant the self-mint guard (inside runMintLive) never ran. A self-mint attempt without --endpoint therefore surfaced a misleading 'instrument doesn't implement mint/burn' error instead of the actionable guard message. transfer/accept similarly returned ErrNeedsV2LocalNet on a live instance when --endpoint was omitted. Resolve the endpoint from the instance's captured participant_ledger_<role> port (as RunBalance already does) in the shared action layer, so both the CLI and Web UI behave consistently and these commands work flag-free on a running LocalNet. Explicit --endpoint still wins; instance-down still falls back to the correct remediation. Align the CLI --endpoint help text with balances/balance. * fix(token): allocate exercises the issuer's on-ledger AllocationFactory `token allocate` posted the scan registry's allocation-factory endpoint, which returns the network-default (DSO/Amulet) factory, then exercised AllocationFactory_Allocate against it. For an issuer-created instrument (token create) that fails on-ledger with AssertionFailed: The requirement 'Instrument-id must match the factory' because the DAML impl asserts allocation.admin == tokenRules.admin and the DSO factory's admin is not the issuer. The issuer's own on-ledger TokenRules contract IS the V2 AllocationFactory (interface instance V2.AllocationFactory for TokenRules), with admin == the instrument admin — exactly as it is the TransferFactory the on-ledger transfer path already uses. Resolve TokenRules via findTokenRules(admin) and exercise AllocationFactory_Allocate against it, building the choice context locally (TokenRules + authorizer AccountConfig cids) and acting as the authorizer's account parties plus the admin. The authorizer Account is taken from the picked holdings' own account so it satisfies the impl's inputHolding.account == allocation.authorizer check (self-custodial and provider-scoped). Verified live on a token-standard-v2 LocalNet: the previously-failing allocate now finalizes an Allocation (committed and non-committed), which `token allocations` lists. Scope limited to allocate; withdraw/cancel/ settle unchanged. * fix(token): withdraw/cancel exercise the issuer's on-ledger Allocation Allocation_Withdraw / Allocation_Cancel were malformed: the choice argument omitted the required `actors : [Party]` controller field (AllocationV2.daml), and the choice context was fetched from the scan registry. The TestToken impl reads the local test-token context from extraArgs (unlockTokenAllocationV2 -> getEventLogFromContext, applyAllocationTransitions -> extractAccountConfigMap), so the registry blob was rejected — the same root cause as the allocate fix. Rework runAllocationAction to mirror the allocate factory path: fetch the target Allocation's view to resolve the admin / authorizer account / executors, resolve the issuer's on-ledger TokenRules (event-log + AccountConfig source), build the local choice context, and exercise with the controller `actors` the state machine requires (AccountConfig.daml): withdraw -> the authorizer's account parties, cancel -> the executors. Act as those actors plus the admin (co-signs the unlocked holdings). Extend the Allocation view walker to surface the authorizer account provider/id and the settlement executors. Drop the now-unused registry withdraw/cancel choice-context paths. Live-verified on a token-standard-v2 LocalNet: fresh allocate -> withdraw and allocate -> cancel each consume the Allocation; a committed allocation correctly refuses early withdrawal. * fix(token): address PR review — activity feed correctness, DvP surface, atomic flag Review fixes for the token V2 tightening PR: - activity: keep the newest maxActivityScan events via a sliding ring buffer instead of breaking at the first cap (which returned the oldest slice). Applies to both the EventLog and netting paths. - activity: dedup paired sender/receiver EventLog_HoldingsChange exercises by (updateID, sorted transferLegIds) so a transfer is not double-counted. - activity: fall through to netting when the EventLog path fails in a fallback-safe way (stream open / malformed event); only a cancelled or expired context aborts. - allocations: emit the shared types.AllocationsResponse from both the CLI JSON and the Web UI handler so the two surfaces cannot drift. - allocations: disable the settle action (CLI, HTTP route, Web UI) until SettlementFactory_SettleBatch is functional on LocalNet; keep withdraw/cancel. - ui: thread the active role through the allocations/transfer/allocate flows; add an experimental atomic-transfer control gated on auto-accept. - docs: record the identity/allocations commands, the experimental --atomic flag, and the deferred settle verb in changes-from-proposal.md. Adds tests for newest-first truncation (both paths), paired-side dedup, and EventLog→netting fallback on stream error vs. context cancellation. --------- Co-authored-by: Zhe Li <linuxcity.jn@gmail.com>
…284) Keep the root README short (quick start, features, thin install) and point users at https://bitdynamics-ab.github.io/canton-devkit/ as the main website; rename website/README.md to DEVELOPMENT.md for Astro maintainer notes.
* docs: restore command reference table in README Re-add the area/commands table after Quick start so GitHub readers can scan the full localnet surface without opening the docs site. * docs: keep feature areas in README without listing commands Restore lifecycle, preflight, app wiring, DAR, ledger, and snapshot bullets in Features; leave specific verbs in the Commands table only. * docs: simplify CLI/UI and observability feature bullets Drop command-flag detail from Features; the Commands table already covers the surface.
* feat(frontend): add Vite mock API for UI-only development Introduce a mock API layer under frontend/mock/ with fixtures, in-memory store, SSE hub, and route handlers so the Web UI can run via `npm run dev:mock` without the Go backend or a LocalNet instance. Wire the plugin into Vite, document mock mode in CONTRIBUTING.md, and add router/seed tests. * fix(frontend): silence unused param in mock router * docs: restructure Web UI dev setup into real vs mock paths * fix(frontend): sync package-lock.json after adding tsx npm ci failed in CI because the lockfile was out of sync with package.json after the mock:seed script added tsx as a devDependency. * fix(frontend): include Linux optional deps in package-lock.json npm ci failed on the self-hosted Linux CI runner because the lockfile was generated on macOS and omitted @emnapi optional peer entries that Linux npm ci requires. * Update mock data
* fix(env): export raw LocalNet JWTs by default * fix(status): expose raw LocalNet JWTs * fix(cli): remove env JWT redaction opt-in * fix(cli): remove status JWT redaction flag * fix(creds): show JWTs in default table output * docs(cli): describe raw JWTs in creds table * fix(ui): return raw JWTs from LocalNet auth APIs * fix(ui): expose raw JWTs in instance details * fix(ui): make JWT API responses raw by default * fix(ui): remove JWT reveal query opt-in * test(ui): keep mock LocalNet JWTs usable * test(env): pin raw JWT export default * test(cli): require raw JWTs without env opt-in * test(status): require raw JWTs by default * test(creds): require JWTs in default table * test(ui): require raw JWT responses by default * test(ui): remove JWT query opt-in expectations * test(ui): pin raw JWT developer setup * docs(ui): remove obsolete JWT query reference * docs(test): clarify raw JWT API contract * test(ui): use raw mock JWT in dashboard * test(mock): provide raw fixture JWTs * test(mock): update LocalNet fixture JWTs * docs(ui): remove obsolete JWT query logging reference * test(ui): update query privacy comment * docs(ui): update credential projection comment * docs: record raw LocalNet JWT defaults * test(ui): seed raw JWT for app config coverage * style: format JWT output changes * docs(test): remove stale redaction wording * docs(test): update env JWT expectation * feat(creds): restore format hint on creds table The raw-JWT table now wraps in a standard terminal because it appends the full token as a trailing column. Restore a footer pointing users to the env/raw formats, which emit clean single-line copy-pasteable output.
Foundation for integrating Certora's daml-analyzer (Apache-2.0), a static analyzer for cross-package interactions in a compiled .dar. internal/analyzer locates a Java runtime + the vendored jar, runs it, and maps the analyzer's JSON onto schema-pinned internal/api/types. The 60MB fat jar is vendored under third_party/ (gitignored here pending the commit-vs-build decision); runtime discovers it by path or DAML_ANALYZER_JAR.
… jar) Package Certora's daml-analyzer as a reproducible container built from a pinned upstream commit, invoked via docker run with the .dar mounted read-only and JSON read from stdout. This drops the 60MB in-git jar and the host Java requirement: the image lives in a registry, pulled on demand. Adds build/daml-analyzer/ (multi-stage Dockerfile + Apache-2.0 LICENSE + README), a manual GHCR publish workflow, and make analyzer-image/analyzer-push. internal/ analyzer now discovers Docker + the image (DAML_ANALYZER_IMAGE override); the report types, parsing, and API are unchanged.
Web UI surface (MountAnalyzer): GET /api/analyzer/status, POST /api/analyzer/
analyze (upload a .dar), and GET /api/instances/{name}/analyzer/{id} (analyze a
deployed DAR by fetching its bytes via the participant admin GetDar). Docker/
image-unavailable maps to a 503 ANALYZER_UNAVAILABLE the UI can render cleanly.
CLI: `dpm localnet dar analyze <dar> [--format text|json]` runs the same
analyzer on a local .dar. Both surfaces go through internal/analyzer, so their
reports are identical (CLI ↔ Web UI parity). Tests drive the real image on a
committed sample DAR and skip when DAML_ANALYZER_IMAGE is unset.
New top-level Analyzer screen: analyze a DAR deployed to the selected instance (pick from its DAR list) or upload a .dar ad hoc, then render the analyzed package, dependency count, by-type summary, and the caller→target cross-package interaction table (consuming choices flagged). Gated on the analyzer being available (Docker + image), with a clean not-configured notice. Wires the nav entry, route, IcAnalyzer icon, and api.ts helpers/ types mirroring internal/api/types/analyzer.go.
Certora now publishes daml-analyzer as a DPM component (oci://ghcr.io/certora/daml-analyzer). Resolve the runtime in priority order: DAML_ANALYZER_BIN, then the DPM-installed component, then the pinned Docker image. The devkit execs the component's wrapper straight from the DPM cache rather than shelling out to `dpm certora-analyze`, because that subcommand only resolves inside a project directory while the devkit runs from anywhere. With the component installed, analysis needs no Docker and nothing vendored — but the component wraps a jar, so a JVM is still required; Status() reports that up front instead of failing mid-analysis. Status is now runtime-agnostic (runtime + source replace the docker/image fields), mirrored in api.ts and the Analyzer tab, which shows the resolved runtime and points at the component install when nothing is available.
Brings the tab up to what the analyzer's own viewer offers, over the same report data: - Highlights: derived findings — consuming exercises, interface implementations, cross-package creates, most-reached package, and interactions missing source info. Clicking one jumps to the filtered list. - Summary: the target-package x interaction-type pivot with per-column and grand totals, a package filter box, and click-through (row = package, cell = package + type) into the interaction list. - Interactions: adds the source file:line column, which the report already carried but the table dropped, plus an active-filter chip. - Diff: compares two loaded reports by interaction identity and shows what a version added, removed, or kept. - Multi-DAR: the upload accepts several .dar files and analyses them in sequence; loaded reports stay selectable, which is what makes diffing possible. Each report can be exported as JSON. Diff and multi-report state live in the screen, so no new endpoints are needed — the backend still analyses one DAR per call.
zheli
marked this pull request as draft
August 7, 2026 09:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Daml DAR analyzer spanning the core runtime, CLI, REST API, and Web UI.
internal/analyzer/): analyzer types + JAR wrapper package; runs the analyzer as a pinned Docker image (not a vendored JAR); prefers the DPM component runtime.dar analyzecommand (internal/cli/localnet/dar/analyze.go).internal/ui/handlers/analyzer.go,internal/api/types/analyzer.go).frontend/src/screens/AnalyzerScreen.tsx).Test plan
internal/analyzer,internal/ui/handlers,internal/cli/localnet/dar,internal/api/types.frontend/src/screens/AnalyzerScreen.test.tsx.