Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions .claude/rules/splice-build-system.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Splice Build System

## SBT Structure

### Key Build Files
- `build.sbt` (~2,300 lines) — root project aggregating 68+ subprojects
- `project/BuildCommon.scala` — shared settings, custom tasks, command aliases
- `project/Dependencies.scala` — Splice library versions (Scala 2.13.16, ScalaTest 3.2.19)
- `project/CantonDependencies.scala` — Canton/Daml SDK versions, Pekko 1.2.1, Slick 3.5.2
- `project/DamlPlugin.scala` (~587 lines) — custom AutoPlugin for DAML compilation
- `project/plugins.sbt` — WartRemover, Scalafmt, ScalaFix, Guardrail (OpenAPI), ScalaPB

### Subproject Categories
- **DAML packages** (36): `splice-amulet-daml`, `splice-wallet-daml`, etc.
- **Scala backends** (7): `apps-common`, `apps-sv`, `apps-validator`, `apps-scan`, `apps-wallet`, `apps-splitwell`, `apps-common-sv`
- **Frontends** (6): `apps-wallet-frontend`, `apps-scan-frontend`, etc.
- **Infrastructure**: `pulumi`, `load-tester`, `token-standard-cli`, `party-allocator`
- **Canton** (vendored): `canton-community-*`, `canton-ledger-*`

### Custom SBT Tasks
```bash
damlBuild # Compile .daml → .dar files
damlTest # Run DAML script tests
damlGenerateCode # Java codegen from DARs
damlDarsLockFileUpdate # Update daml/dars.lock
updateDarResources # Regenerate DarResources.scala
bundle # Create splice-node.tar.gz release
updateTestConfigForParallelRuns # Update test-*.log parallelization files
```

### Command Aliases
```bash
format # scalafmt ; Test/scalafmt ; scalafmtSbt
formatFix # format + scalafixAll + apps-frontends/npmFix + headerCreate
lint # Full lint check (formats, scalafix, npm lint, copyright, shellcheck)
clean-splice # Clean Splice modules only (not Canton)
```

## DAR (DAML Archive) Management

### Build Output
- `.daml/dist/splice-{name}-{version}.dar` — versioned output
- `.daml/dist/splice-{name}-current.dar` — symlink to latest
- `daml/dars/*.dar` — committed DARs for stable package IDs
- `daml/dars.lock` — package ID lock file (CI-enforced)

### DarResources Generator
- Input: built DARs
- Output: `apps/common/src/main/scala/.../DarResources.scala` (auto-generated)
- Contains: `packageResources`, `pkgIdToDarResource`, `pkgMetadataToDarResource`
- Task: `sbt updateDarResources`

### DAR Change Workflow
1. Edit `.daml` files
2. Bump version in `daml.yaml` + recursive dependents
3. `sbt damlBuild; sbt damlDarsLockFileUpdate`
4. Update versions in `DarResources.scala` manually
5. `sbt updateDarResources`
6. Update `apps/package.json` DAML package versions
7. `sbt npmInstall; sbt compile`

## Nix / direnv

### Dev Environment
- `nix/flake.nix` — defines dev shells: `default` (enterprise), `oss`, `static_tests`
- `nix/shell.nix` — 114+ packages (SBT, Scala, openjdk21, K8s tools, Python3, Pulumi)
- `nix/canton-sources.json` — Canton version + Docker image SHA digests
- `nix/daml-compiler-sources.json` — DAML compiler version

### Environment Variables (from direnv)
- `CANTON` — path to Canton binary
- `CANTON_VERSION` — from canton-sources.json
- `DAML_COMPILER_VERSION` — from daml-compiler-sources.json
- Private vars in `.envrc.private` (gitignored): `ARTIFACTORY_USER`, `ARTIFACTORY_PASSWORD`, `GH_USER`, `GH_TOKEN`, Auth0 credentials

## Code Formatting

### Scalafmt
- Config: `.scalafmt.conf` (version 3.7.1)
- Max column: 100
- Trailing commas: `multiple` (diff-friendly)
- Scala 3 syntax conversion enabled (except `.sbt` and `project/`)

### Scalafix
- Semantic analysis for automated refactoring
- Run: `sbt scalafixAll`

### TypeScript
- Prettier via `scripts/fix-ts.py`
- ESLint per workspace

### Copyright Headers
- Apache-2.0 SPDX on all source files
- Enforced: `sbt headerCreate` / `sbt headerCheck`

## Pre-commit Hooks

`.pre-commit-config.yaml` defines:
1. `copyright` — SBT header validation
2. `scalafmt` — Scala formatting
3. `sbt_tests` — update test parallelization config on test file changes
4. `dars_lock` — update DAR lock on `.daml`/`daml.yaml` changes
5. `no_illegal_daml_references` — DAML reference validation
6. `shellcheck` — shell script linting
7. `typescriptfmt` — TypeScript/JS formatting
8. `pulumi_tests` — Pulumi config validation
9. `gha_lint` — GitHub Actions YAML linting
10. `check-trailing-whitespace`
11. `check-daml-warts` — DAML code smell detection
12. `check-daml-return-types` — return type validation
13. `check-grafana-dashboards` — dashboard JSON validation
14. `rstcheck` — Sphinx RST doc validation

## CI (GitHub Actions)

### Main Orchestrator: `.github/workflows/build.yml`
- Opt-in via `[ci]` commit tag (default: cancelled)
- Jobs: `static_tests`, `deployment_test`, `daml_test`, `scala_test_*` (9+ parallel suites), `typescript_tests`, `ui_tests`, `build_container`
- Self-hosted K8s runners: small/medium/x-large

### Static Tests: `.github/workflows/build.static_tests.yml`
- Canton consistency, scalafmt, scalafix, prettier, DAML warts/return types, TODO validation, shellcheck, actionlint, RST docs

### Scala Test Template: `.github/workflows/build.scala_test.yml`
- Reusable template with parallelism, PostgreSQL service, GCP workload identity
- Test categories tracked in `test-full-class-names-*.log` files

### Test Category Files
`test-full-class-names-sim-time.log`, `test-full-class-names-resource-intensive.log`, `test-full-class-names-disaster-recovery.log`, etc.
- Updated by `sbt updateTestConfigForParallelRuns` when adding new tests
- Must be committed with test additions

## Version Management

### Version File: `/VERSION` (currently `0.6.0`)
### Snapshot Format: `{base}-{YYYYMMDD}.{commit_count}.{commit_sha_8}`
### Release Detection: `[release]` commit tag or `release/*` branch

## Docker Images

### Registry: `ghcr.io/digital-asset/decentralized-canton-sync/docker`
### Frontend Images: Multi-stage (app build → nginx:1.29.0 unprivileged)
### Base Image Pinning: All base images pinned by SHA256 digest
### Config Injection: Runtime `envsubst` generates `config.js` → `window.splice_config`

## Dependency Bumping

### Canton Bump (significant effort)
See `MAINTENANCE.md` — involves patching our fork, reapplying changes, version alignment.
Key files: `nix/canton-sources.json`, `project/CantonDependencies.scala`

### DAML Compiler Bump
Update `nix/daml-compiler-sources.json` — changes all package IDs (requires governance vote).

### CometBFT Bump
Update `nix/cometbft-driver-sources.json`, vendor new proto JAR.
177 changes: 177 additions & 0 deletions .claude/rules/splice-conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# Splice Conventions

## Git & PR Workflow

### Branch Naming
`<yourname>/<descriptivename>` (e.g., `bob/fix-footest/4242`)

### Merge Strategy
**Squash and merge** — PR title becomes the single commit on `main`.

### Squash Commit Message
- Title: concise, imperative mood ("Add external hash to Scan API", not "Added...")
- Body: brief summary of what/why, bullet points OK
- Reference issue: `Fixes #1234` or `Part of #1234`
- Remove CI tags (`[ci]`, `[static]`)
- Must include `Signed-off-by:` line (DCO)

### CI Opt-in
- `[ci]` in commit message — run full CI
- `[static]` — static tests only
- `[force]` — skip CI but allow merge
- `[breaking]` — skip upgrade compatibility tests

## Naming Conventions

### Domain Terms
- Use `amount` (not `quantity` or `number`) consistently
- Use `sender`/`receiver` (not `payer`/`payee`)
- Use `listXXX`, `acceptXXX`, `rejectXXX`, `withdrawXXX` for proposals/requests
- Flags: `enableXXX` (not `disableXXX` — avoid double negation)

### Protobuf
- Use `proto3` syntax
- Store contract IDs as `string` with `contract_id` suffix
- Store party IDs as `string` with `party_id` suffix
- Use plural names for `repeated` fields
- Avoid wrapping primitives in messages unless future extensibility needed
- Place `.proto` files in `src/main/protobuf`
- Prefer single `.proto` per service
- Reference generated Protobuf classes with package prefix (`v0.MyMessage`)

### Code Layout
- Place `.proto` files in `src/main/protobuf`
- Generated protobuf: reference with package prefix to avoid name conflicts

## DAML Conventions

### Changes Require Governance Vote
All DAML model changes on production require SV majority vote. Discuss with maintainers first.

### Lock Files
- `daml/dars.lock` — CI validates package IDs match
- Update after changes: `sbt damlDarsLockFileUpdate`

### Backwards Compatibility (Required)
- All changes must be backwards-compatible
- `ExtFoo` constructors are legacy — add new constructors directly now
- Enums must remain enums (all nullary constructors) — don't add non-nullary constructors
- See [Canton upgrading docs](https://docs.digitalasset.com/build/3.4/sdlc-howtos/smart-contracts/upgrade/)

### Numerics
- User-facing APIs: `scala.math.BigDecimal` (auto-converts from Int/Float)
- Protobuf: `string` (no native BigDecimal type). Convert via `Proto.encode/tryDecode`
- Ledger API: convert to `java.math.BigDecimal`
- Canonical example: `wallet.tap` command implementation

### Version Guards
- When DAML version X+1 is compiled but not yet voted in, backends must handle version X
- Tag tests: `@SpliceAmulet_0_1_9` for version-dependent tests
- Check active version endpoints in triggers and UIs

### Deprecation Rule
Wait for DAML version to be **effective on mainnet for one month** before bumping minimum and cleaning up code. Only if removal is not user-facing.

## Scala Conventions

### Java/Scala Type Conversions
- Use Scala types wherever possible
- Convert at boundaries only: `import scala.jdk.CollectionConverters.*` → `.asScala` / `.asJava`
- Delay Java conversion to last possible point, convert from Java as early as possible

### Unused Import Warnings
- Can be suppressed locally by creating `.disable-unused-warnings` file + `sbt reload`
- Not for CI — only during development

## Database Migrations

### Location
`apps/common/src/main/resources/db/migration/canton-network/postgres/stable/`

### Rules
1. **IMMUTABLE** — never modify deployed migration scripts (Flyway checksum verification)
2. Naming: `VXXX__descriptive_title.sql`
3. New columns: make NULLABLE for backward compat; populate from JSONB
4. Re-ingestion: bump store descriptor `version` to force ACS re-ingest
5. Filter changes: increment store descriptor version

### Schema Patterns
- `store_descriptors` table — tracks all ACS/TxLog stores (JSONB)
- Template tables: `{app}_acs_store` inherits from `acs_store_template`
- Index naming: `{table}_sid_mid_pn_tid_{column_abbrev}` (sid=store_id, mid=migration_id, etc.)
- Partial indexes exclude NULL values

### Store Descriptor
```scala
StoreDescriptor(version: Int, name: String, party: PartyId, participant: ParticipantId, key: Map[String, String])
```
Bump `version` when schema or contract filters change.

## OpenAPI / Scan API

### BFT-Consensus Types (Critical)
Update history types are serialized to JSON and compared across SVs for BFT consensus. **Any** schema change breaks consensus.

### Adding Fields to BFT Types
- **Required fields**: Gate behind threshold record time so all SVs switch simultaneously
- **Optional fields**: Use `Option[OmitNullString]` + `omitWhenNone` helper (not bare `Option[_]` — circe emits `null` which breaks JSON equality)
- Add new types to `UpdateHistoryOmitNullStringComplianceTest`
- See `ScanJsonSupport.scala` for custom encoders

## Configuration (HOCON)

### Rules
- Only environment variables are fully supported for overrides (not system props)
- Use `${?ENV_VAR}` substitution syntax
- Config printed via `scripts/print-config.sh FILE`

## Frontend

### Workspace Structure
- Root: `apps/package.json` — npm workspaces
- Shared lib: `apps/common/frontend/` (`common-frontend`)
- Each app: own `tsconfig.json` inheriting from root
- Required scripts per package: `build`, `fix`, `check`, `start`

### Common Libs
- Import: `import { ... } from '@lfdecentralizedtrust/splice-common-frontend'`
- Uses barreling technique (`index.ts` exports all modules)
- Install: `npm install @lfdecentralizedtrust/splice-common-frontend -w my-workspace-pkg`

### Auth
- RS256 (OIDC) for production, HS256 (unsafe) for local dev
- Config via `window.splice_config` runtime injection
- `react-oidc-context` for OIDC flow

## Logging

### Format
- `.clog` extension (Canton Log format, JSON structured)
- `--log-encoder json --log-file-name log/splice-node-{suffix}.clog`
- NamedLoggerFactory with context propagation
- OpenTelemetry tracing integration

### Inspection
- `lnav log/canton_network_test.clog` (provided by direnv)
- Filter: `:filter-in config=<config-id>`, `:filter-out RequestLogger`
- SQL: `:filter-expr :log_text LIKE '%pattern%'`

## Metrics

### Framework
- Prometheus export via admin API
- OpenTelemetry exponential histograms (160 max buckets)
- Base: `SpliceMetrics` → per-app `{App}Metrics`
- Per-trigger: `TriggerMetrics` (latency, iterations, completions)
- Per-store: `StoreMetrics` (ACS size, ingestion rate)

### Definition Pattern
```scala
val myMetric: Timer = metricsFactory.timer(
MetricInfo(prefix :+ "my-metric", "Description", Latency, "Details...")
)
```

## TODO Comments
- Format: `TODO(#ISSUE_NUMBER): description`
- Tracked by `scripts/check-todos.sh` in CI
Loading