@mcsherrylabs/pi-meerkat 0.6.0 → 0.8.0

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 (67) hide show
  1. package/README.md +234 -54
  2. package/dist/installer/committee-init.sh +117 -0
  3. package/dist/installer/committee.conf +102 -0
  4. package/dist/installer/node-join.sh +228 -0
  5. package/dist/installer/node.conf +235 -0
  6. package/dist/installer/runtime.json +1 -0
  7. package/dist/installer-runtime/dist/scripts/recover-owner.js +15 -0
  8. package/dist/installer-runtime/dist/scripts/subscriptions-init.js +35 -0
  9. package/dist/installer-runtime/dist/scripts/versions.mjs +86 -0
  10. package/dist/installer-runtime/dist/scripts/wait-for-chain.js +90 -0
  11. package/dist/installer-runtime/dist/src/announcements.js +448 -0
  12. package/dist/installer-runtime/dist/src/app.js +539 -0
  13. package/dist/installer-runtime/dist/src/chainfetch.js +87 -0
  14. package/dist/installer-runtime/dist/src/env.js +425 -0
  15. package/dist/installer-runtime/dist/src/registry/announce.js +77 -0
  16. package/dist/installer-runtime/dist/src/registry/announcement.js +141 -0
  17. package/dist/installer-runtime/dist/src/registry/dependencies.js +28 -0
  18. package/dist/installer-runtime/dist/src/registry/detail.js +87 -0
  19. package/dist/installer-runtime/dist/src/registry/drainer.js +131 -0
  20. package/dist/installer-runtime/dist/src/registry/http.js +194 -0
  21. package/dist/installer-runtime/dist/src/registry/index.js +52 -0
  22. package/dist/installer-runtime/dist/src/registry/lease.js +35 -0
  23. package/dist/installer-runtime/dist/src/registry/ownership.js +31 -0
  24. package/dist/installer-runtime/dist/src/registry/provision.js +180 -0
  25. package/dist/installer-runtime/dist/src/registry/store.js +242 -0
  26. package/dist/installer-runtime/dist/src/registry/sweep.js +9 -0
  27. package/dist/installer-runtime/dist/src/registry/worker-module.js +87 -0
  28. package/dist/installer-runtime/dist/src/runtime.js +164 -0
  29. package/dist/installer-runtime/dist/src/store.js +138 -0
  30. package/dist/installer-runtime/package.json +1 -0
  31. package/dist/installer.d.ts +224 -0
  32. package/dist/installer.d.ts.map +1 -0
  33. package/dist/installer.js +336 -0
  34. package/dist/installer.js.map +1 -0
  35. package/dist/mcp-announce.d.ts +42 -0
  36. package/dist/mcp-announce.d.ts.map +1 -0
  37. package/dist/mcp-announce.js +231 -0
  38. package/dist/mcp-announce.js.map +1 -0
  39. package/dist/mcp-cli.d.ts +3 -0
  40. package/dist/mcp-cli.d.ts.map +1 -0
  41. package/dist/mcp-cli.js +137 -0
  42. package/dist/mcp-cli.js.map +1 -0
  43. package/dist/mcp-config.d.ts +28 -0
  44. package/dist/mcp-config.d.ts.map +1 -0
  45. package/dist/mcp-config.js +104 -0
  46. package/dist/mcp-config.js.map +1 -0
  47. package/dist/mcp-facade.d.ts +20 -0
  48. package/dist/mcp-facade.d.ts.map +1 -0
  49. package/dist/mcp-facade.js +200 -0
  50. package/dist/mcp-facade.js.map +1 -0
  51. package/dist/mcp-upstream.d.ts +42 -0
  52. package/dist/mcp-upstream.d.ts.map +1 -0
  53. package/dist/mcp-upstream.js +151 -0
  54. package/dist/mcp-upstream.js.map +1 -0
  55. package/dist/registry.d.ts.map +1 -1
  56. package/dist/registry.js +11 -5
  57. package/dist/registry.js.map +1 -1
  58. package/dist/tools.d.ts +6 -2
  59. package/dist/tools.d.ts.map +1 -1
  60. package/dist/tools.js +123 -119
  61. package/dist/tools.js.map +1 -1
  62. package/installer/Committee.Dockerfile +6 -0
  63. package/installer/Node.Dockerfile +6 -0
  64. package/installer/Registry.Dockerfile +13 -0
  65. package/installer/runtime-control.mjs +172 -0
  66. package/installer/session-files.mjs +16 -0
  67. package/package.json +14 -5
package/README.md CHANGED
@@ -1,75 +1,255 @@
1
1
  # @mcsherrylabs/pi-meerkat
2
2
 
3
- Pi agent tools for the meerkat announcements registry: **announce a channel you control**, and
4
- search what others have announced.
3
+ Meerkat registry tools and a **stdio MCP facade**. One client registration exposes the seven
4
+ Meerkat tools plus every tool dynamically discovered from the private
5
+ `@mcsherrylabs/evolver-mcp@0.15.0` child. The released upstream baseline is 65 tools, so healthy
6
+ discovery currently returns 72. The count is a comparison baseline, not a routing allowlist.
5
7
 
6
- ```
7
- pi-meerkat ──▶ @mcsherrylabs/evolver-core ──▶ @mcsherrylabs/evolver-client
8
- the service's verbs chain + content capability the wire
9
- ```
8
+ **Version 0.8.0 adds the zero-required-parameter installer.** Version 0.7.0 introduced the
9
+ facade but required an existing node, registry, identity and key. Version 0.6.0 is the old
10
+ Pi/local-signing package. Always use an exact published version, not npm `latest`.
10
11
 
11
- `pi-meerkat` depends on `@mcsherrylabs/evolver-core` (the extracted chain capability that the MCP
12
- server and any agent extension share) rather than carrying a vendored copy. Two things stay local
13
- because core deliberately does not provide them: the **two-file project/home config precedence**
14
- (`src/config.ts`) and the **chain target** (`src/target.ts`, the node client + signer wiring). The
15
- failure taxonomy (`ToolResult`) and the content publish (`publishBytes`) come from core.
12
+ ## Install the complete experience
16
13
 
17
- ## Why this is a separate package
14
+ Prerequisites in the environment where your MCP harness runs:
18
15
 
19
- The generic chain tool surface forbids a tool from naming a channel, a ledger or a service, and fails its build
20
- for it — a draft of its content tool was rejected for the word "announcement". That rule is what
21
- keeps it chain-generic, and it is also what says where a service's own verbs belong: **here**, in
22
- the service's own repository, installed by whoever wants them. A new service becomes usable by an
23
- agent with nothing central edited.
16
+ - Node.js **22 or newer**, with `npm` and `npx` on `PATH`.
17
+ - A reachable Docker Engine and **Docker Compose v2** (`docker version`, `docker compose version`).
18
+ A normal local engine or a separately hosted Docker daemon/context both work. Existing Docker
19
+ authentication/TLS settings are respected; no new credentials are requested for public images.
20
+ - An MCP harness with stdio support. The installer prints registration JSON; it does not edit
21
+ the harness or silently replace an existing registration.
24
22
 
25
- ## Tools
23
+ ### 1. Install
26
24
 
27
- | tool | what it answers |
28
- |---|---|
29
- | `meerkat_summary` | start here — counts, kinds, keywords, misses, and **which channel** this registry indexes |
30
- | `meerkat_services` | search by keyword or free text |
31
- | `meerkat_service` | one entry in full, with its `provenance` |
32
- | `meerkat_detail` | the announced document, digest-verified by the registry |
33
- | `meerkat_rejections` | why an announcement was refused |
34
- | `meerkat_announce` | describe a channel you control, optionally with a document |
35
- | `meerkat_inbox` | what arrived on a channel this node subscribes to and no handler claimed — the event inbox |
25
+ ```bash
26
+ npx --yes @mcsherrylabs/pi-meerkat@0.8.0 install
27
+ ```
36
28
 
37
- ## Three things that are deliberate
29
+ No identity, key, endpoint, registry, chain ID, channel, port, GID, path, environment variables
30
+ or paid model service is required. This creates an **independent private local chain** and
31
+ starts its owned consensus node, separate processing node and Meerkat registry/content/inbox services. It does **not** join the
32
+ shared devnet, claim from its faucet, inspect unrelated keys, or change another deployment.
33
+ The first install downloads public pinned artifacts and builds three wrapper images, so
34
+ allow time for Docker/npm downloads. Failed preflight/build/readiness exits nonzero with a reason.
38
35
 
39
- **The document is published before the announcement, inside the tool.** An announcement names its
40
- document by key and digest. Announce first and the entry refers to bytes that exist nowhere —
41
- permanently `unfetchable`, indistinguishable from a document that is merely slow, and *every*
42
- detail document on this estate was in that state until it was found. Two composable tools would
43
- put that ordering in a model's hands. `meerkat_announce` takes the text.
36
+ ### 2. Register one server
44
37
 
45
- **The document is published OPEN.** Anyone who obtains the key may read it. That is right for a
46
- public index, and with the announcements channel no longer carrying a DA group there is no group
47
- left to grant to. Do not announce anything whose disclosure depends on a key staying private.
38
+ Use the one `registration.mcpServers.meerkat` entry printed by installation. For Claude Code,
39
+ if you do not already have a registration named `meerkat`, the equivalent command is:
48
40
 
49
- **The channel is configuration, not a parameter.** `REGISTRY_CHANNEL` — the same variable the
50
- registry reads, so the two cannot disagree. A model with a free-text channel field will
51
- eventually fill it from something it read, and an announcement is charged and permanent.
41
+ ```bash
42
+ claude mcp add --scope user --transport stdio meerkat -- \
43
+ npx --yes @mcsherrylabs/pi-meerkat@0.8.0 serve
44
+ ```
52
45
 
53
- ## Configuration
46
+ Do not remove or replace an unrelated `meerkat` registration. Choose a different harness
47
+ registration name for this entry if necessary; its underlying saved deployment remains the same.
48
+ Other MCP clients can add the printed stdio JSON entry directly. Do **not** register an Evolver
49
+ child separately. Restart the harness connection, inspect its tools, and begin with
50
+ `meerkat_summary`, `meerkat_services`, `meerkat_inbox` and `evolver_chain_height`.
51
+
52
+ `serve` finds the installer's saved state automatically; there is no `--config` or environment
53
+ ceremony. The public package launcher is pinned and survives npm-cache cleanup. Each client
54
+ gets its **own facade and exact Evolver child/session/store** inside the owned runtime container,
55
+ connected through Docker exec stdio. Node/content/registry HTTP stays on the private owned
56
+ Docker network, with **no host-published ports**, so a separate Docker daemon needs neither
57
+ host-path sharing nor a public read port.
58
+ Stdout carries MCP protocol only. Closing the connection or signalling the launcher awaits the
59
+ exact remote facade and its private child, not the shared registry or another client's session.
60
+
61
+ ### Resume and ownership
62
+
63
+ Run the **same install command** again to resume the same installation. Names, identity/key
64
+ references, internal service ports, daemon identity, real genesis/node binding and ownership are saved
65
+ privately under the operator's XDG state directory (or normal home state directory). Services
66
+ use separately owned Docker volumes for consensus data, processing-node data, public genesis,
67
+ registry/app state, subscriptions and announcer state. Consensus and consumer-paced processing
68
+ are separate roles required by the released node. The follower receives only the **public** chain
69
+ specification, never the consensus node's private volume. The released Evolver bootstrap alone
70
+ creates/loads announcer signing keys; the facade,
71
+ installer and output carry only references. No key bytes are copied to the harness.
72
+
73
+ Rerun does not initialize subscriptions again, regenerate genesis, rotate keys, change authority,
74
+ force-claim/unclaim, adopt another daemon's resources or recover an owner lock. A missing valued
75
+ volume, conflicting ownership, changed daemon/binding, interrupted initializer or stale owner lock
76
+ refuses explicitly and retains state for operator inspection. There is no automatic reset,
77
+ force recovery, Docker prune, broad restart or harness-registration mutation. `serve` never
78
+ creates or restarts services. An intentional **install** invocation may start its own stopped
79
+ services; unrelated services are never touched. Automatic cross-version migration is not provided:
80
+ resume with the saved pinned version.
81
+
82
+ The installer enables the complete registry/read/announce experience, not every optional upstream
83
+ capability. Generic Evolver subscription-control and other optional capability tools can remain
84
+ unconfigured and refuse clearly; no Docker socket or private node-control API is exposed to the
85
+ registry/facade. The real tool catalog baseline is 65 upstream + 7 local, **not a promise that each
86
+ optional capability is configured**.
87
+
88
+ ## Advanced: attach an existing node
89
+
90
+ If you already operate a node, registry and keys, you may install this exact package normally
91
+ and use `meerkat-mcp setup` instead. This is an alternative to the automatic installer, not a
92
+ prerequisite for it. Keep your installed package on a persistent user-owned PATH, not an
93
+ npm-cache-internal pathname.
94
+
95
+ Setup records **references**, not key bytes. Select an existing node endpoint, separate content
96
+ endpoint, identity, existing key file, registry read origin, explicit genesis/chain ID and node ID,
97
+ registry channel, and TLS policy. This command does not provision a node, claim an identity,
98
+ create/copy a key, initialize a registry database, or change a client registration automatically.
99
+
100
+ Set the following shell inputs to values you already operate. Do not copy a guessed identity,
101
+ chain ID, key, or endpoint from a model or from registry content. `MEERKAT_TLS_INSECURE=false`
102
+ keeps certificate verification enabled; an explicitly chosen private CA may be supplied with
103
+ `--ca-file /absolute/operator-ca.pem`.
104
+
105
+ <!-- meerkat-setup:start -->
106
+ ```bash
107
+ meerkat-mcp setup \
108
+ --config "${MEERKAT_CONFIG:?Choose an absolute operator-owned config path}" \
109
+ --node-url "${MEERKAT_NODE_URL:?Select the existing node origin}" \
110
+ --content-url "${MEERKAT_CONTENT_URL:?Select its separate content origin}" \
111
+ --identity "${MEERKAT_IDENTITY:?Select the existing signing identity}" \
112
+ --key-path "${MEERKAT_KEY_PATH:?Select the existing absolute key reference}" \
113
+ --key-type "${MEERKAT_KEY_TYPE:?Select ed25519 or bls}" \
114
+ --tls-insecure "${MEERKAT_TLS_INSECURE:?Explicitly choose true or false}" \
115
+ --meerkat-url "${MEERKAT_READ_URL:?Select the registry processor read origin}" \
116
+ --chain-id "${MEERKAT_CHAIN_ID:?Supply the existing 64-hex chain ID}" \
117
+ --node-id "${MEERKAT_NODE_ID:?Supply the existing inbox-binding node ID}" \
118
+ --channel "${MEERKAT_CHANNEL:?Select the registry announcement channel}"
119
+ ```
120
+ <!-- meerkat-setup:end -->
54
121
 
55
- | variable | what |
56
- |---|---|
57
- | `MEERKAT_URL` | the event processor's read server — the registry's routes and the inbox — e.g. `http://registry:8090`. `REGISTRY_URL`, its name before 0.3.0, is refused: every read answers `misconfigured` until it is renamed |
58
- | `REGISTRY_CHANNEL` | the channel to announce onto and read (default `ce.meerkat.announcements`) |
59
- | `EVOLVER_URL`, `EVOLVER_IDENTITY`, `EVOLVER_KEY_PATH` | the local loader — who signs |
60
- | `EVOLVER_CONTENT_URL` | the local loader — required only to announce a document |
122
+ Create the config's parent directory beforehand. Setup exclusively creates an operator-owned
123
+ mode-0600 file and refuses to overwrite an existing one. Treat that file and all parent paths as
124
+ trusted launch configuration; do not let a project or another user replace them. Setup checks
125
+ that the key reference exists but **only the child reads signing-key bytes**. Key bytes do not
126
+ enter the setup output, facade, or registration.
127
+
128
+ Setup emits one JSON `mcpServers` entry, with the installed facade's absolute command and
129
+ `--config` pointing to the saved file. Add **that one Meerkat entry** to your MCP client's
130
+ configuration. No manual environment ceremony, separate Evolver registration, HTTP MCP service,
131
+ Pi adapter, or separately managed child is required. Direct launch uses the same entrypoint:
132
+
133
+ ```bash
134
+ meerkat-mcp --config "$MEERKAT_CONFIG"
135
+ ```
61
136
 
62
- The read tools need no chain at all: an agent with no signing key can still search the index.
137
+ For advanced attach, register the exact persistent installed entrypoint/config returned by
138
+ setup; do not point a client at an npm-cache-internal file. The automatic installer's pinned
139
+ `serve` registration above is the recommended route for a virgin harness. In Claude Code,
140
+ `--scope user` makes the operator registration available across projects; `--scope local`
141
+ limits it to the current project. Start a fresh session and use `/mcp` to inspect the connection.
142
+ Do not approve announcement/sign/submit calls until you deliberately choose to exercise a write.
143
+
144
+ The stdio connection uses stdout exclusively for MCP messages. Diagnostics go to stderr. The
145
+ operator-selected node/content/trust/identity/key/channel inputs are captured at facade startup;
146
+ model arguments and project configuration cannot select signing authority. Within one client,
147
+ forwarded Evolver tools and `meerkat_announce` deliberately share the owned upstream session and
148
+ signing history. Independent clients each get a distinct child/session/store. There are **two
149
+ server OS processes**, privately owned by the single registration. Close the client connection
150
+ or stop the facade to await its child and transport cleanup.
151
+
152
+ ## Degraded discovery and independent reads
153
+
154
+ Initialization and local reads do not require a signing key or a working upstream child.
155
+ Without `--config`, registry read inputs retain `MEERKAT_URL`, `EVOLVER_CHAIN_ID` and
156
+ `APP_NODE_ID`; these are the only inputs needed for bound inbox reads. With `--config`, the
157
+ saved operator values bind those reads. `REGISTRY_URL` remains a refused obsolete name.
158
+
159
+ A bounded failed upstream discovery returns the **seven local definitions** with explicit
160
+ upstream-unavailable/degraded metadata and a stderr diagnostic. All six read tools remain
161
+ usable, including both inbox summary and paginated page routes. Announce and upstream-dependent
162
+ calls refuse clearly. Failed refresh invalidates stale upstream routing; a later successful
163
+ catalog discovery can restore healthy routing but never resumes or replays an interrupted write.
164
+ There is no hidden-name workaround or eighth status tool.
63
165
 
64
- ## The contract lives here
166
+ ## Tools
65
167
 
66
- `src/contract.ts` is the announcement wire format — bounds, types and the body builder. It is in
67
- this package rather than in the app so a client can announce without depending on the whole
68
- registry, and so there is one encoding rather than two. Pure: no chain, no clock, no I/O.
168
+ | Tool | Purpose |
169
+ |---|---|
170
+ | `meerkat_summary` | Start here: counts, kinds, keywords, misses, indexed channel and progress |
171
+ | `meerkat_services` | Search announced services by keyword or free text |
172
+ | `meerkat_service` | Read one service, including provenance |
173
+ | `meerkat_detail` | Read the document already digest-verified by the registry |
174
+ | `meerkat_rejections` | Learn why an ordered announcement was not indexed |
175
+ | `meerkat_inbox` | Read the inbox summary, or a bounded page with `round:index` continuation |
176
+ | `meerkat_announce` | Describe a channel you control, optionally with an open document |
177
+
178
+ The original seven names, descriptions and input schemas are preserved against published 0.6.0.
179
+ Upstream descriptions/schemas/results are forwarded unchanged. A newly discovered tool routes
180
+ without an adapter. Colliding names and unknown/undiscovered calls refuse rather than shadowing.
181
+ This is a tools facade, not a generic prompts/resources proxy. Supported cancellation is passed
182
+ through; cancellation is not rollback. Supported upstream tool-list changes refresh the catalog.
183
+
184
+ Registry strings are **untrusted data**, never instructions or signing configuration. Read errors
185
+ are not empty answers: not-found, unreachable, unavailable and truncated remain distinct. Inbox
186
+ binding validates the configured chain and node; previews remain at most 256 decoded bytes per
187
+ entry, with at most 50 entries and 12,800 preview bytes per page. Carry the returned `next` as
188
+ `after` yourself; the facade does not advance a processor cursor.
189
+
190
+ ## Announcement semantics
191
+
192
+ The registry channel is **trusted configuration**, not a tool parameter. The facade uses the
193
+ existing unsigned `announcementBody` encoder, with its field order, bounds, UTF-8 bytes and
194
+ 4,096-byte body cap. Sequence is epoch seconds; lease expiry uses a read-only chain-round
195
+ preflight in the same session, defaulting to 20,000 rounds, never wall-clock as chain height.
196
+
197
+ The owned sequence is:
198
+
199
+ 1. `evolver_chain_height` (read-only preflight).
200
+ 2. If nonempty detail text is supplied, await `evolver_content_put` with `access=open`; validate
201
+ the real acknowledgement and original UTF-8 size/SHA-256 before embedding its reference.
202
+ 3. `evolver_sign_channel_write` for the configured channel and exact announcement `bodyHex`.
203
+ 4. Ordinary `evolver_submit` with **only the original returned txId**.
204
+
205
+ No DA conversion/collection, auto-claim, extra mutating setup, caller-selected identity/key/TTL,
206
+ local signer, local key read or direct node submit is added. The document is published **OPEN**:
207
+ anyone with its key may read it. Upload occurs before signing; a successful upload may remain
208
+ stored after a later failure.
209
+
210
+ Only this owned orchestration decodes upstream's actual warning-suffixed JSON and submit prose;
211
+ transparent forwarded results are never reworded. Protocol success or `isError:false` does not
212
+ establish a committed announcement. Committed, fined, refused, unknown and local
213
+ already-submitted remain distinct. A fined write can charge and deliver a body; committed is
214
+ **not registry acceptance**—read the entry/rejections afterward.
215
+
216
+ Definitive local child/session loss before submit prevents that send. Once submit may have left
217
+ the transport, a lost, timed-out or unreadable reply means **UNKNOWN**, retaining the original
218
+ txId and reconciliation guidance. Read/reconcile that transaction; never automatically reconnect,
219
+ resume, re-sign, retry or replay the write. Already-submitted says this call sent nothing, not
220
+ that a previous attempt failed.
221
+
222
+ ## Existing Pi package and registry application
223
+
224
+ The retained Pi extension and useful `channel-subscription` / `service-discovery` skills are
225
+ still shipped. The legacy extension's `EVOLVER_URL`, `EVOLVER_IDENTITY`, `EVOLVER_KEY_PATH`,
226
+ `EVOLVER_CONTENT_URL` and two-file Pi config are **not** the MCP facade's authority interface.
227
+ No additional Pi host adapter is required. The root registry application, database/schema,
228
+ processor cursor and supported SDK Docker helper are unchanged. Root `npm start` starts the
229
+ registry application and does not demonstrate facade acceptance.
230
+
231
+ The announcement wire contract remains in this package (`src/contract.ts`, export `./contract`),
232
+ so clients and the registry use one encoder rather than divergent copies. MCP runtime
233
+ capability comes from the exact installed child; remaining SDK usage in the facade is unsigned
234
+ encoding/types or the existing bound `ProcessorClient` inbox reads, not local signing.
235
+
236
+ ## Verification lanes
69
237
 
70
238
  ```bash
71
- npm run check:contract # the encoding and every bound, with nothing running
72
- npm run check:surface # the tool table's properties
239
+ npm run check:pi # root: ordinary package tests, including K01–K07
240
+ npm run check:plan-two:client --workspace @mcsherrylabs/pi-meerkat
73
241
  ```
74
242
 
75
- Version 0.4.2 (history) pinned pi-evolver 0.9.0 and evolver-client 0.19.0 to share the corrected identity-key lookup. Configure the agent API endpoint to evolver-node **v0.24.1+** before using that lookup. The announcement and inbox contracts are unchanged.
243
+ Run networked gates in a disposable isolated network namespace/container, with physical
244
+ `K_REGISTRY_TEST_TMP` and temporary outputs outside the source tree. K uses a real downstream
245
+ MCP client, built and freshly packed-installed facade, real released child, fixed fixture keys,
246
+ HTTP boundary stand-ins, explicit counters/barriers, and awaited exact-owned cleanup. It makes
247
+ no live model, faucet/devnet, production database or estate changes. `PLAN_TWO_REPORT` selects
248
+ the acceptance report path; source CI retains this report and runs the normal package chain.
249
+
250
+ An npm pack is **not publication**. Publication needs an unused version, synchronized locks,
251
+ an immutable `pi-meerkat-vX.Y.Z` tag at the accepted revision and successful exact-SHA required
252
+ CI/publish jobs. Published-release verification additionally retrieves anonymous metadata and
253
+ tarballs/integrity and exercises the installed published facade/dependency with independent
254
+ clients in isolation. Served/downloaded instruction hashes and transcripts belong to that
255
+ post-publication lane. Pending publication or delivery gates must not be represented as passes.
@@ -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