@mcsherrylabs/pi-meerkat 0.7.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +109 -33
  2. package/dist/installer/committee-init.sh +117 -0
  3. package/dist/installer/committee.conf +102 -0
  4. package/dist/installer/genesis.devnet.conf +92 -0
  5. package/dist/installer/node-join.sh +235 -0
  6. package/dist/installer/node.conf +238 -0
  7. package/dist/installer/node.devnet.conf +238 -0
  8. package/dist/installer/runtime.json +1 -0
  9. package/dist/installer-runtime/dist/scripts/recover-owner.js +15 -0
  10. package/dist/installer-runtime/dist/scripts/subscriptions-init.js +35 -0
  11. package/dist/installer-runtime/dist/scripts/versions.mjs +86 -0
  12. package/dist/installer-runtime/dist/scripts/wait-for-chain.js +90 -0
  13. package/dist/installer-runtime/dist/src/announcements.js +448 -0
  14. package/dist/installer-runtime/dist/src/app.js +552 -0
  15. package/dist/installer-runtime/dist/src/chainfetch.js +87 -0
  16. package/dist/installer-runtime/dist/src/env.js +437 -0
  17. package/dist/installer-runtime/dist/src/registry/announce.js +77 -0
  18. package/dist/installer-runtime/dist/src/registry/announcement.js +141 -0
  19. package/dist/installer-runtime/dist/src/registry/dependencies.js +28 -0
  20. package/dist/installer-runtime/dist/src/registry/detail.js +87 -0
  21. package/dist/installer-runtime/dist/src/registry/drainer.js +131 -0
  22. package/dist/installer-runtime/dist/src/registry/http.js +194 -0
  23. package/dist/installer-runtime/dist/src/registry/index.js +61 -0
  24. package/dist/installer-runtime/dist/src/registry/lease.js +35 -0
  25. package/dist/installer-runtime/dist/src/registry/ownership.js +31 -0
  26. package/dist/installer-runtime/dist/src/registry/provision.js +225 -0
  27. package/dist/installer-runtime/dist/src/registry/store.js +242 -0
  28. package/dist/installer-runtime/dist/src/registry/sweep.js +9 -0
  29. package/dist/installer-runtime/dist/src/registry/worker-module.js +87 -0
  30. package/dist/installer-runtime/dist/src/runtime.js +192 -0
  31. package/dist/installer-runtime/dist/src/store.js +138 -0
  32. package/dist/installer-runtime/package.json +1 -0
  33. package/dist/installer.d.ts +401 -0
  34. package/dist/installer.d.ts.map +1 -0
  35. package/dist/installer.js +446 -0
  36. package/dist/installer.js.map +1 -0
  37. package/dist/mcp-announce.d.ts +18 -0
  38. package/dist/mcp-announce.d.ts.map +1 -1
  39. package/dist/mcp-announce.js +18 -4
  40. package/dist/mcp-announce.js.map +1 -1
  41. package/dist/mcp-cli.d.ts.map +1 -1
  42. package/dist/mcp-cli.js +41 -5
  43. package/dist/mcp-cli.js.map +1 -1
  44. package/dist/mcp-config.d.ts +1 -0
  45. package/dist/mcp-config.d.ts.map +1 -1
  46. package/dist/mcp-config.js +2 -1
  47. package/dist/mcp-config.js.map +1 -1
  48. package/dist/mcp-upstream.d.ts +1 -1
  49. package/dist/mcp-upstream.d.ts.map +1 -1
  50. package/dist/mcp-upstream.js +7 -2
  51. package/dist/mcp-upstream.js.map +1 -1
  52. package/dist/registry.d.ts.map +1 -1
  53. package/dist/registry.js +8 -4
  54. package/dist/registry.js.map +1 -1
  55. package/dist/tools.js +4 -4
  56. package/dist/tools.js.map +1 -1
  57. package/installer/Committee.Dockerfile +6 -0
  58. package/installer/Node.Dockerfile +10 -0
  59. package/installer/Registry.Dockerfile +13 -0
  60. package/installer/runtime-control.mjs +335 -0
  61. package/installer/session-files.mjs +16 -0
  62. package/package.json +8 -7
  63. package/skills/channel-subscription/SKILL.md +3 -3
package/README.md CHANGED
@@ -5,28 +5,116 @@ Meerkat tools plus every tool dynamically discovered from the private
5
5
  `@mcsherrylabs/evolver-mcp@0.15.0` child. The released upstream baseline is 65 tools, so healthy
6
6
  discovery currently returns 72. The count is a comparison baseline, not a routing allowlist.
7
7
 
8
- **Version 0.7.0 introduces this facade.** Version 0.6.0 is the previous Pi/local-signing package
9
- and does **not** contain it. Use the explicit version below, not an unversioned `npx` command or
10
- an assumption about npm `latest`. Source/packed acceptance, publication, and verification of an
11
- installed published release are separate milestones.
8
+ **Version 0.9.0 makes DevNet the default install target** (`install` installs against the public
9
+ DevNet; `install --local` creates a private local chain). Version 0.8.0 added the
10
+ zero-required-parameter installer (local chain only). Version 0.7.0 introduced the
11
+ facade but required an existing node, registry, identity and key. Version 0.6.0 is the old
12
+ Pi/local-signing package. Always use an exact published version, not npm `latest`.
12
13
 
13
- ## Install from npm
14
+ ## Install the complete experience
14
15
 
15
- Run this in the same environment where Claude Code (or another MCP client) runs. Node 22 is
16
- the verified runtime. Installation downloads the package and its exact released dependency;
17
- it does not provision or start a node:
16
+ Prerequisites in the environment where your MCP harness runs:
17
+
18
+ - Node.js **22 or newer**, with `npm` and `npx` on `PATH`.
19
+ - A reachable Docker Engine and **Docker Compose v2** (`docker version`, `docker compose version`).
20
+ A normal local engine or a separately hosted Docker daemon/context both work. Existing Docker
21
+ authentication/TLS settings are respected; no new credentials are requested for public images.
22
+ - An MCP harness with stdio support. The installer prints registration JSON; it does not edit
23
+ the harness or silently replace an existing registration.
24
+
25
+ ### 1. Install
18
26
 
19
27
  ```bash
20
- npm install --prefix "$HOME/.local/share/meerkat-mcp/0.7.0" \
21
- --no-audit --no-fund @mcsherrylabs/pi-meerkat@0.7.0
22
- export PATH="$HOME/.local/share/meerkat-mcp/0.7.0/node_modules/.bin:$PATH"
28
+ npx --yes @mcsherrylabs/pi-meerkat@0.9.0 install # public DevNet (default)
29
+ npx --yes @mcsherrylabs/pi-meerkat@0.9.0 install --local # private local chain
23
30
  ```
24
31
 
25
- The installed facade resolves and launches its actual dependency's `evolver-mcp` bin
26
- (`dist/cli.js`). It does not use a sibling source checkout, global executable, or unversioned
27
- runtime download. Installation under the home directory persists across rig recreation.
32
+ No identity, key, endpoint, registry, chain ID, channel, port, GID, path, environment variables
33
+ or paid model service is required.
34
+
35
+ **Default (DevNet)** installs a single non-genesis follower node against the public DevNet, syncing
36
+ its shared chain, and a Meerkat registry that reads and indexes the shared, pinned
37
+ `ce.meerkat.announcements` channel (owned by the pinned `evolvermeerkatA483344` operator). That
38
+ registry runs **read-only**: it never claims, signs or submits with any identity. DevNet catch-up
39
+ syncs from block 0 (~14k blocks at the time of release) and is paced by the registry's own cursor
40
+ drain, so the first install can take a while; it reports progress and fails only if sync stalls.
41
+
42
+ A DevNet install **permanently claims two DevNet identities**, both funded by the public DevNet
43
+ faucet: its node (`meerkatnode<id>`) and your agent (`meerkatuser<id>`), which signs your
44
+ announcements. Names on DevNet cannot be reused. Re-running install never claims again. DevNet mode
45
+ needs **outbound internet access** from Docker (the node dials DevNet's front nodes, and the faucet
46
+ is reached over HTTPS); it still publishes no host ports.
47
+
48
+ **`--local`** creates an **independent private local chain** — an owned consensus node, a separate
49
+ processing node, and a Meerkat registry/content/inbox service. The agent's own identity
50
+ (`meerkatuser<id>`) owns the install's private `meerkat.<id>.announcements` channel (claimed once,
51
+ idempotently), and nothing touches DevNet. In either mode the registry claims no identity, and the
52
+ installer never inspects unrelated keys or changes another deployment. The first install downloads public pinned artifacts and
53
+ builds the wrapper images, so allow time for Docker/npm downloads. Failed
54
+ preflight/build/readiness exits nonzero with a reason. Each mode keeps its own state directory
55
+ (`devnet/` or `local/` under your XDG state directory); 0.8.0 state is never touched.
28
56
 
29
- ## Configure an existing node once
57
+ ### 2. Register one server
58
+
59
+ Use the one `registration.mcpServers.meerkat` entry printed by installation. For Claude Code,
60
+ if you do not already have a registration named `meerkat`, the equivalent command is:
61
+
62
+ ```bash
63
+ claude mcp add --scope user --transport stdio meerkat -- \
64
+ npx --yes @mcsherrylabs/pi-meerkat@0.9.0 serve
65
+ ```
66
+
67
+ Add `--local` after `serve` if you installed with `install --local`.
68
+
69
+ Do not remove or replace an unrelated `meerkat` registration. Choose a different harness
70
+ registration name for this entry if necessary; its underlying saved deployment remains the same.
71
+ Other MCP clients can add the printed stdio JSON entry directly. Do **not** register an Evolver
72
+ child separately. Restart the harness connection, inspect its tools, and begin with
73
+ `meerkat_summary`, `meerkat_services`, `meerkat_inbox` and `evolver_chain_height`.
74
+
75
+ `serve` finds the installer's saved state automatically (default `devnet`, `--local` for the
76
+ private chain); there is no `--config` or environment ceremony. The public package launcher is
77
+ pinned and survives npm-cache cleanup. Each client
78
+ gets its **own facade and exact Evolver child/session/store** inside the owned runtime container,
79
+ connected through Docker exec stdio. Node/content/registry HTTP stays on the private owned
80
+ Docker network, with **no host-published ports**, so a separate Docker daemon needs neither
81
+ host-path sharing nor a public read port.
82
+ Stdout carries MCP protocol only. Closing the connection or signalling the launcher awaits the
83
+ exact remote facade and its private child, not the shared registry or another client's session.
84
+
85
+ ### Resume and ownership
86
+
87
+ Run the **same install command** again to resume the same installation. Names, identity/key
88
+ references, internal service ports, daemon identity, real genesis/node binding and ownership are saved
89
+ privately under the operator's XDG state directory (or normal home state directory). Services
90
+ use separately owned Docker volumes for consensus data, processing-node data, public genesis,
91
+ registry/app state, subscriptions and announcer state. Consensus and consumer-paced processing
92
+ are separate roles required by the released node. The follower receives only the **public** chain
93
+ specification, never the consensus node's private volume. The released Evolver bootstrap alone
94
+ creates/loads announcer signing keys; the facade,
95
+ installer and output carry only references. No key bytes are copied to the harness.
96
+
97
+ Rerun does not initialize subscriptions again, regenerate genesis, rotate keys, change authority,
98
+ force-claim/unclaim, adopt another daemon's resources or recover an owner lock. A missing valued
99
+ volume, conflicting ownership, changed daemon/binding, interrupted initializer or stale owner lock
100
+ refuses explicitly and retains state for operator inspection. There is no automatic reset,
101
+ force recovery, Docker prune, broad restart or harness-registration mutation. `serve` never
102
+ creates or restarts services. An intentional **install** invocation may start its own stopped
103
+ services; unrelated services are never touched. Automatic cross-version migration is not provided:
104
+ resume with the saved pinned version.
105
+
106
+ The installer enables the complete registry/read/announce experience, not every optional upstream
107
+ capability. Generic Evolver subscription-control and other optional capability tools can remain
108
+ unconfigured and refuse clearly; no Docker socket or private node-control API is exposed to the
109
+ registry/facade. The real tool catalog baseline is 65 upstream + 7 local, **not a promise that each
110
+ optional capability is configured**.
111
+
112
+ ## Advanced: attach an existing node
113
+
114
+ If you already operate a node, registry and keys, you may install this exact package normally
115
+ and use `meerkat-mcp setup` instead. This is an alternative to the automatic installer, not a
116
+ prerequisite for it. Keep your installed package on a persistent user-owned PATH, not an
117
+ npm-cache-internal pathname.
30
118
 
31
119
  Setup records **references**, not key bytes. Select an existing node endpoint, separate content
32
120
  endpoint, identity, existing key file, registry read origin, explicit genesis/chain ID and node ID,
@@ -70,24 +158,12 @@ Pi adapter, or separately managed child is required. Direct launch uses the same
70
158
  meerkat-mcp --config "$MEERKAT_CONFIG"
71
159
  ```
72
160
 
73
- ### Register in Claude Code
74
-
75
- From the environment where Claude Code runs, register the installed facade with the same
76
- absolute config path used for setup:
77
-
78
- ```bash
79
- claude mcp add --scope user --transport stdio meerkat -- \
80
- "$(command -v node)" \
81
- "$HOME/.local/share/meerkat-mcp/0.7.0/node_modules/@mcsherrylabs/pi-meerkat/dist/mcp-cli.js" \
82
- --config "$MEERKAT_CONFIG"
83
- ```
84
-
85
- `--scope user` makes this operator registration available across your projects. Use
86
- `--scope local` instead to restrict the initial test to the current project. Start a fresh
87
- Claude Code session and run `/mcp` to inspect the connection and its tools. Do not register the
88
- private Evolver child separately. Begin with `meerkat_summary`, a small `meerkat_services`
89
- query, `meerkat_inbox`, and the upstream read-only `evolver_chain_height`; do not approve
90
- announcement/sign/submit calls until you deliberately choose to exercise a write.
161
+ For advanced attach, register the exact persistent installed entrypoint/config returned by
162
+ setup; do not point a client at an npm-cache-internal file. The automatic installer's pinned
163
+ `serve` registration above is the recommended route for a virgin harness. In Claude Code,
164
+ `--scope user` makes the operator registration available across projects; `--scope local`
165
+ limits it to the current project. Start a fresh session and use `/mcp` to inspect the connection.
166
+ Do not approve announcement/sign/submit calls until you deliberately choose to exercise a write.
91
167
 
92
168
  The stdio connection uses stdout exclusively for MCP messages. Diagnostics go to stderr. The
93
169
  operator-selected node/content/trust/identity/key/channel inputs are captured at facade startup;
@@ -0,0 +1,117 @@
1
+ #!/bin/sh
2
+ # node entrypoint: host keys + genesis on first boot, then exec the node.
3
+ #
4
+ # On FIRST boot it generates this node's key material ON THE HOST (never in the
5
+ # image or the repo), renders a one-node genesis committee from the derived public
6
+ # keys, and persists everything into the /data volume. On every subsequent boot it
7
+ # REUSES the same keys + genesis so the chain continues — the genesis committee must
8
+ # stay byte-stable or the chain identity diverges.
9
+ #
10
+ # Two secrets, handled identically (spec 101 — both are host-only secrets):
11
+ # - Ed25519 node-identity key -> ce.evolver.node.private-key (EVOLVER_PRIVATE_KEY)
12
+ # - BLS consensus key (IKM) -> ce.evolver.node.bls-secret (EVOLVER_BLS_SEED)
13
+ # Their public keys go into genesis (public-key / bls-public-key).
14
+ set -eu
15
+
16
+ # Deliberately NOT derived from the app name. This id is written into the genesis
17
+ # file on the volume at FIRST BOOT, so changing it on an existing chain would launch
18
+ # the node under a name absent from its own genesis. Keeping it neutral means
19
+ # `npm run rename` never has to touch this file and cannot leave a previous project's
20
+ # name behind — the template used to default to `tmplnode0`, so every app built from
21
+ # it had a node introducing itself by the TEMPLATE's name, in its logs and in its
22
+ # published deployment artifact.
23
+ NODE_ID="${NODE_ID:-node0}"
24
+ KEYS=/data/keys
25
+ GENESIS=/data/genesis.gen.conf
26
+ ED_KEY="$KEYS/ed25519.key" # base64 PKCS#8 Ed25519 private
27
+ BLS_KEY="$KEYS/bls.key" # base64 BLS IKM (>=32 bytes)
28
+
29
+ if [ -f "$ED_KEY" ] || [ -f "$BLS_KEY" ] || [ -f "$GENESIS" ]; then
30
+ [ -s "$ED_KEY" ] && [ -s "$BLS_KEY" ] && [ -s "$GENESIS" ] || {
31
+ echo '[node-init] incomplete owned keys/genesis; refusing regeneration' >&2; exit 1;
32
+ }
33
+ fi
34
+ if [ ! -f "$ED_KEY" ]; then
35
+ echo "[node-init] first boot: generating host key material for '$NODE_ID'"
36
+ mkdir -p "$KEYS"; chmod 700 "$KEYS"
37
+
38
+ # Ed25519 identity keypair — PKCS#8 private + X.509 public, base64 DER.
39
+ tmp="$(mktemp)"
40
+ openssl genpkey -algorithm ed25519 -outform DER -out "$tmp" 2>/dev/null
41
+ ED_PRIV="$(base64 -w0 < "$tmp")"
42
+ ED_PUB="$(openssl pkey -inform DER -in "$tmp" -pubout -outform DER 2>/dev/null | base64 -w0)"
43
+ rm -f "$tmp"
44
+
45
+ # BLS consensus keypair — random IKM + derived pubkey, via the evolver keygen.
46
+ BLS_OUT="$(java -cp "/opt/evolver/lib/*" ce.evolver.crypto.keys.BLSKeyGenerator)"
47
+ BLS_SECRET="$(echo "$BLS_OUT" | awk '/^bls-secret:/{print $2}')"
48
+ BLS_PUB="$(echo "$BLS_OUT" | awk '/^bls-public-key:/{print $2}')"
49
+
50
+ if [ -z "$ED_PRIV" ] || [ -z "$ED_PUB" ] || [ -z "$BLS_SECRET" ] || [ -z "$BLS_PUB" ]; then
51
+ echo "[node-init] FATAL: key generation produced empty output" >&2
52
+ exit 1
53
+ fi
54
+
55
+ # Persist secrets — host-only, mode 600.
56
+ printf '%s' "$ED_PRIV" > "$ED_KEY"; chmod 600 "$ED_KEY"
57
+ printf '%s' "$BLS_SECRET" > "$BLS_KEY"; chmod 600 "$BLS_KEY"
58
+
59
+ # Render the chain spec (089): committee PUBLIC keys + the chain-wide consensus
60
+ # parameters. This exact file is published verbatim to every follower / app node
61
+ # (102 follower-join: the genesis file IS the chain, byte-identical everywhere) and
62
+ # its canonical hash is pinned as the genesis block id (ce.evolver.genesis-file in
63
+ # node.conf). 103: the host is the ROUTABLE public address — followers dial the
64
+ # committee at exactly this host:port. Chain-wide params live HERE, not in node.conf,
65
+ # so joiners inherit them automatically — quiescence especially (FR-019: a joiner
66
+ # with different quiescence would time out the deliberately-lingering leader).
67
+ PUBLIC_HOST="${PUBLIC_HOST:-localhost}"
68
+ cat > "$GENESIS" <<EOF
69
+ ce.evolver.genesis.nodes = [
70
+ {
71
+ node-id = "$NODE_ID"
72
+ host = "$PUBLIC_HOST"
73
+ port = 7000
74
+ public-key = "$ED_PUB"
75
+ bls-public-key = "$BLS_PUB"
76
+ quorum-weight = 1000
77
+ }
78
+ ]
79
+
80
+ ce.evolver.consensus {
81
+ n-nodes = 1
82
+ block-time-ms = 600
83
+ round-timeout-ms = 1500
84
+ block-reward-tokens = 10000 # generous dev reward: the faucet (5000/identity) is funded from the FIRST blocks, even under quiescence
85
+ snapshot-interval-blocks = 0 # single-node: no snapshot cadence; joiners sync from block 0
86
+ identity-sponsorship-cost = 100
87
+
88
+ # Low idle disk-write: after 3 empty blocks the leader lingers on a 5-MINUTE heartbeat
89
+ # instead of the 600ms cadence (#31: idle-heartbeat-ms > block-time-ms activates quiescence);
90
+ # wake-on-work (node-local, always on) still proposes immediately on incoming txs.
91
+ # CHAIN-WIDE — identical on every node by construction.
92
+ quiescence {
93
+ idle-heartbeat-ms = 300000
94
+ idle-after-empty-rounds = 3
95
+ }
96
+ }
97
+ EOF
98
+ echo "[node-init] generated keys (secrets in $KEYS, mode 600) + chain spec (host=$PUBLIC_HOST)"
99
+ echo "[node-init] PUBLISH the chain spec (public keys + chain-wide config): docker compose cp node:/data/genesis.gen.conf ."
100
+ else
101
+ echo "[node-init] existing keys found in $KEYS — reusing (chain continues)"
102
+ fi
103
+
104
+ # Secrets reach the node via env (HOCON ${?...} substitution in node.conf).
105
+ EVOLVER_PRIVATE_KEY="$(cat "$ED_KEY")"; export EVOLVER_PRIVATE_KEY
106
+ EVOLVER_BLS_SEED="$(cat "$BLS_KEY")"; export EVOLVER_BLS_SEED
107
+ mkdir -p /data/chain
108
+
109
+ echo "[node-init] launching evolver node '$NODE_ID'"
110
+ if [ -f /public-genesis/genesis.conf ]; then
111
+ cmp -s "$GENESIS" /public-genesis/genesis.conf || { echo '[node-init] public genesis differs; refusing replacement' >&2; exit 1; }
112
+ else
113
+ cp "$GENESIS" /public-genesis/genesis.pending
114
+ chmod 640 /public-genesis/genesis.pending
115
+ mv /public-genesis/genesis.pending /public-genesis/genesis.conf
116
+ fi
117
+ exec /opt/evolver/bin/evolver -Dconfig.file=/config/node.conf "$NODE_ID"
@@ -0,0 +1,102 @@
1
+ # Single-node full-quorum, quiescent. The chain spec (committee with this
2
+ # node's generated public keys + ALL chain-wide consensus params, incl. quiescence) is
3
+ # rendered at first boot by node-init.sh into /data/genesis.gen.conf and included
4
+ # here — this file holds only NODE-LOCAL config. Secrets arrive via env (never in this
5
+ # file).
6
+ include file("/data/genesis.gen.conf")
7
+
8
+ # 089 chain-spec pin: the canonical hash of the genesis file IS the chain's genesis
9
+ # block id. Joiners set this to their verbatim copy — any drift refuses to boot.
10
+ ce.evolver.genesis-file = "/data/genesis.gen.conf"
11
+
12
+ ce.evolver.node {
13
+ private-key = ${?EVOLVER_PRIVATE_KEY} # Ed25519 PKCS#8 (host secret, from node-init.sh)
14
+ bls-secret = ${?EVOLVER_BLS_SEED} # BLS IKM (host secret, from node-init.sh)
15
+ }
16
+
17
+ evolver.peer-discovery {
18
+ # What this node ADVERTISES about ITSELF to peers — not somewhere it connects to.
19
+ # This chain is standalone (one node, its own genesis, no seeds) so nothing reads
20
+ # these; localhost is used because the alternative is advertising an address that
21
+ # is not this node. They named devnet.mcsherrylabs.com purely because this config
22
+ # was copied from that deployment. Joining a real chain means setting them to the
23
+ # ROUTABLE p2p transport (raw TCP mTLS :7000) and HTTPS API your peers can dial.
24
+ # 163 — `host:port`, no scheme: the transport is raw TCP + mTLS and never was a URL.
25
+ transport-address = "committee:7000"
26
+ # No `peer-url`: this chain is one node, so nothing fetches content from it, and the
27
+ # plain-HTTP :8440 above could not serve a peer anyway — a content fetch carries the
28
+ # caller's client certificate and needs the node's OWN TLS listener. Joining a real
29
+ # chain means setting `transport-address` to the routable :7000 and `peer-url` to this
30
+ # node's https listener (never a reverse proxy: it presents no client certificate).
31
+ gossip-interval-seconds = 60
32
+ }
33
+
34
+ # Chain-wide consensus params (n-nodes, block-time, quiescence, ...) come from the
35
+ # included chain spec — do NOT set them here, they must stay byte-identical on every
36
+ # node. Only node-local knobs below.
37
+ ce.evolver.consensus {
38
+ validate-chain-on-startup = false
39
+
40
+ # Reactivity (node-local, always on since #28): on incoming work the leader proposes
41
+ # IMMEDIATELY rather than waiting out the (5-minute) idle heartbeat — tx latency stays
42
+ # sub-second despite slow idle.
43
+ wake-on-work {
44
+ forward-watermark = 16
45
+ }
46
+ }
47
+
48
+ data.base = "/data/chain"
49
+ state.snapshots = "snapshots"
50
+ # PLAIN HTTP on 8440 for everything that TALKS to this node, deliberately. The HTTPS
51
+ # listener presents the node's IDENTITY certificate — self-signed, and valid for ~13
52
+ # MINUTES, because it is a peer authentication credential and not a web certificate. No
53
+ # browser can be made happy with that: an exception you click through expires before you
54
+ # finish reading the page. Tolerating it in code means NODE_TLS_REJECT_UNAUTHORIZED=0,
55
+ # which disables verification PROCESS-WIDE for every host — a bad habit to ship in a
56
+ # template.
57
+ #
58
+ # TLS on 8441 for PEERS, because the content cache needs somewhere mTLS to live: its peer
59
+ # route identifies callers by client certificate and the node refuses to start content
60
+ # without a TLS listener. Before evolver v0.16.0 those were the same listener, so
61
+ # "hold documents" and "put every client on an unpinnable certificate" were one decision.
62
+ # They are not, and this is what that separation looks like in a deployment.
63
+ http.port = 8440
64
+ # ⚠️ REQUIRED since evolver #131: the plain API listener now defaults to 127.0.0.1. The app
65
+ # container reaches this node over the CONTAINER NETWORK, which is not loopback from the
66
+ # node's point of view — without this line the app gets connection refused and the node
67
+ # logs nothing unusual. "0.0.0.0" is what this deployment effectively had before.
68
+ http.bind-host = "0.0.0.0"
69
+ https.port = 8441
70
+ evolver.grpc.port = 9000
71
+
72
+ # 139 — CONTENT, so a detail document announced on this chain can actually be fetched.
73
+ #
74
+ # `bind-host` is what lets a SIBLING CONTAINER reach the publish/read route: it is
75
+ # authorized by what can reach it, and the default (loopback) means "inside this node's
76
+ # container and nowhere else". 0.0.0.0 here says: this compose project's network is the
77
+ # boundary. It holds because the port below is published to 127.0.0.1 only.
78
+ ce.evolver.content {
79
+ enabled = true
80
+ http-port = 8450
81
+ bind-host = "0.0.0.0"
82
+ }
83
+
84
+ chainDb {
85
+ datasource {
86
+ driver = "org.hsqldb.jdbc.JDBCDriver"
87
+ user = "sa"
88
+ pass = ""
89
+ maxPoolSize = 5
90
+ connectionProperties = ""
91
+ cachePrepStmts = true
92
+ prepStmtCacheSize = 250
93
+ prepStmtCacheSqlLimit = 2048
94
+ connectionTimeout = 5000
95
+ useServerPrepStmts = false
96
+ transactionIsolationLevel = "TRANSACTION_READ_COMMITTED"
97
+ }
98
+ viewCachesSize = 50
99
+ useShutdownHook = false
100
+ freeBlobsEarly = false
101
+ }
102
+ ce.evolver.execution.enabled = false
@@ -0,0 +1,92 @@
1
+ # devnet genesis — the committee, plus SEED NODES (170).
2
+ #
3
+ # ⚠️ THIS FILE IS THE CHAIN'S IDENTITY. Its canonical hash is the genesis block id, so any edit
4
+ # here produces a DIFFERENT CHAIN. Every node must hold a byte-identical copy; a node with an
5
+ # older one does not disagree politely, it cannot join at all.
6
+ #
7
+ # ── Why the seed entries exist (170, issue #121) ────────────────────────────────────────────
8
+ # A node starting with an empty volume can only learn a peer's ADDRESS from this file, and can
9
+ # only VERIFY a peer whose key is already in its chain state — which, before it has synced, is
10
+ # also only this file. Both roads lead here. Before 170 this file named one node, the committee,
11
+ # whose `allowed-inbound` is deliberately full, so a newcomer could reach nothing it was able to
12
+ # trust and nothing it was able to trust would accept it. No configuration could fix that.
13
+ #
14
+ # A `quorum-weight = 0` entry is present-and-trusted WITHOUT being on the committee: it is seeded
15
+ # as an identity, its Ed25519 key becomes verifiable, and its address becomes dialable — while
16
+ # `seedGenesis` keeps it out of the quorum store and leader rotation filters it out of the
17
+ # proposer set. That is the whole mechanism; no new key, no new trust path.
18
+ #
19
+ # ⚠️ The two front nodes keep the names they already had. They were NOT in genesis before, so a
20
+ # chain rebuild would have reassigned their names and invalidated every pin across the estate and
21
+ # the published installer. Pinning them here makes the names stable by construction.
22
+ ce.evolver.genesis.nodes = [
23
+ # ── the committee ──────────────────────────────────────────────────────────────────────────
24
+ {
25
+ node-id = "devnet1-node0"
26
+ host = "devnet.mcsherrylabs.com"
27
+ port = 7000
28
+ public-key = "MCowBQYDK2VwAyEAxTC3mipBz3Ae6nkUzORWLAco21eX3iSSC+QEUSbJMCs="
29
+ bls-public-key = "rCrpHeLH85ECkow6A58I54fjvtCmOHfdm1llPrish+aD49l32sBciyYAzytRxwak"
30
+ quorum-weight = 1000
31
+ },
32
+
33
+ # ── seed nodes: weight 0, so present and trusted but never voting, never leading ───────────
34
+ #
35
+ # ⚠️ Keys are the ones these two nodes ALREADY hold, read from their own /status. Their host
36
+ # secrets are untouched and stay where they are — only the public halves appear here.
37
+ {
38
+ node-id = "evolvermeerkatnode920080"
39
+ host = "65.21.255.224"
40
+ port = 7100
41
+ public-key = "MCowBQYDK2VwAyEAvDNSSDsSYRHfvFCOcl/Fba89VnBrkLzYBy28xqjg8Zg="
42
+ bls-public-key = "iJZMbE+J+TnNnHpYxk2lQDJipTvvUg0qCQNVJViTfoatqZkqazLCZbUmSP4W26Vj"
43
+ quorum-weight = 0
44
+ },
45
+ {
46
+ node-id = "schednode2"
47
+ host = "65.21.255.224"
48
+ port = 7200
49
+ public-key = "MCowBQYDK2VwAyEAvUOp3fA1nTjv546jme0n14wYDVjHshLL3I7XpQxQ8V4="
50
+ bls-public-key = "ht3U8qRoQtKIU0NUbuuwx/ni4iF+rcAs2dJgxDOcf+YOs+brlSKSnC2XBFBp1rR1"
51
+ quorum-weight = 0
52
+ },
53
+
54
+ # ⚠️ devnet-seed-3 IS NEVER DEPLOYED, ON PURPOSE, AND MUST STAY THAT WAY. A list of seeds is
55
+ # only worth having if a dead entry is walked past rather than waited on, and a seed that is
56
+ # permanently absent tests that on every single join instead of on the one afternoon somebody
57
+ # remembers to stop a live one. Its private key material was generated in a throwaway container
58
+ # and destroyed with it — nobody can bring this node up, which is the point. `.invalid` is
59
+ # reserved by RFC 2606 and can never resolve, so the attempt fails fast instead of hanging.
60
+ # If you are reading this because something reports it unreachable: that is the test passing.
61
+ {
62
+ node-id = "devnet-seed-3"
63
+ host = "devnet-seed-3.invalid"
64
+ port = 7300
65
+ public-key = "MCowBQYDK2VwAyEAHY2onS4+fZss0VZaT84AMcQQugILPagvBm3PFDE5cYY="
66
+ bls-public-key = "oO91EyEt4jvpP3XuuNiT+6F/9djCjAqgs/+e7eI64eGz9iRCcHT5HY17coLLKfsk"
67
+ quorum-weight = 0
68
+ }
69
+ ]
70
+
71
+ ce.evolver.consensus {
72
+ # ⚠️ ONE voting node. This is the committee size, not the number of genesis entries — the three
73
+ # seed entries above carry weight 0 and are not counted (NodeBootstrap derives the consensus
74
+ # cluster size with the same `quorumWeight > 0` filter the quorum store and leader rotation
75
+ # already use; before 170 it counted every entry, which would have put the relay threshold
76
+ # beyond anything a one-member committee could reach).
77
+ n-nodes = 1
78
+ block-time-ms = 600
79
+ round-timeout-ms = 1500
80
+ block-reward-tokens = 10000 # generous dev reward: the faucet (5000/identity) is funded from the FIRST blocks, even under quiescence
81
+ snapshot-interval-blocks = 0 # single-node: no snapshot cadence; joiners sync from block 0
82
+ identity-sponsorship-cost = 100
83
+
84
+ # Low idle disk-write: after 3 empty blocks the leader lingers on a 5-MINUTE heartbeat
85
+ # instead of the 600ms cadence (#31: idle-heartbeat-ms > block-time-ms activates quiescence);
86
+ # wake-on-work (node-local, always on) still proposes immediately on incoming txs.
87
+ # CHAIN-WIDE — identical on every node by construction.
88
+ quiescence {
89
+ idle-heartbeat-ms = 300000
90
+ idle-after-empty-rounds = 3
91
+ }
92
+ }