@mcsherrylabs/pi-meerkat 0.8.0 → 0.11.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 (47) hide show
  1. package/README.md +34 -10
  2. package/dist/installer/committee-init.sh +17 -33
  3. package/dist/installer/committee.conf +12 -34
  4. package/dist/installer/genesis.devnet.conf +92 -0
  5. package/dist/installer/node-follower.conf +232 -0
  6. package/dist/installer/node-join.sh +15 -1
  7. package/dist/installer/node.conf +4 -231
  8. package/dist/installer/node.local.conf +8 -0
  9. package/dist/installer/runtime.json +1 -1
  10. package/dist/installer-runtime/dist/scripts/recover-owner.js +7 -9
  11. package/dist/installer-runtime/dist/scripts/subscriptions-init.js +3 -4
  12. package/dist/installer-runtime/dist/scripts/versions.mjs +5 -10
  13. package/dist/installer-runtime/dist/src/announcements.js +1 -289
  14. package/dist/installer-runtime/dist/src/app.js +12 -535
  15. package/dist/installer-runtime/dist/src/chainfetch.js +0 -64
  16. package/dist/installer-runtime/dist/src/env.js +47 -32
  17. package/dist/installer-runtime/dist/src/registry/index.js +11 -2
  18. package/dist/installer-runtime/dist/src/registry/provision.js +46 -1
  19. package/dist/installer-runtime/dist/src/service.js +151 -0
  20. package/dist/installer.d.ts +185 -18
  21. package/dist/installer.d.ts.map +1 -1
  22. package/dist/installer.js +161 -52
  23. package/dist/installer.js.map +1 -1
  24. package/dist/mcp-announce.d.ts +18 -0
  25. package/dist/mcp-announce.d.ts.map +1 -1
  26. package/dist/mcp-announce.js +18 -4
  27. package/dist/mcp-announce.js.map +1 -1
  28. package/dist/mcp-cli.d.ts.map +1 -1
  29. package/dist/mcp-cli.js +28 -18
  30. package/dist/mcp-cli.js.map +1 -1
  31. package/dist/mcp-config.d.ts +1 -0
  32. package/dist/mcp-config.d.ts.map +1 -1
  33. package/dist/mcp-config.js +2 -1
  34. package/dist/mcp-config.js.map +1 -1
  35. package/dist/mcp-upstream.d.ts +1 -1
  36. package/dist/mcp-upstream.d.ts.map +1 -1
  37. package/dist/mcp-upstream.js +7 -2
  38. package/dist/mcp-upstream.js.map +1 -1
  39. package/dist/tools.js +4 -4
  40. package/dist/tools.js.map +1 -1
  41. package/installer/Node.Dockerfile +5 -0
  42. package/installer/Registry.Dockerfile +34 -9
  43. package/installer/check-single-sdk.mjs +151 -0
  44. package/installer/runtime-control.mjs +179 -16
  45. package/package.json +5 -5
  46. package/skills/channel-subscription/SKILL.md +3 -3
  47. package/dist/installer-runtime/dist/src/runtime.js +0 -164
@@ -86,8 +86,8 @@ export const CHAIN_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,31}$/;
86
86
  * `??` is the wrong operator for these. Compose renders a pass-through variable as
87
87
  * PRESENT-AND-EMPTY rather than omitting it (`EVOLVER_DEVNET_URL: ${EVOLVER_DEVNET_URL:-}`),
88
88
  * and `process.env.X ?? default` only falls back on undefined — so an empty value WINS and
89
- * the default never applies. That is not hypothetical: `docker-compose.remote.yml` was the
90
- * first caller to pass these through, devnet's url resolved to the empty string, and the
89
+ * the default never applies. That is not hypothetical: a pass-through deploy that rendered
90
+ * these present-and-empty once made devnet's url resolve to the empty string, and the
91
91
  * deployment failed with `chain not ready at (target 'devnet')`. Every optional variable
92
92
  * in this project goes through here. */
93
93
  export function optionalEnv(k) {
@@ -350,42 +350,57 @@ export function registryChannel() {
350
350
  export function registryChannelOperator() {
351
351
  return optionalEnv("REGISTRY_CHANNEL_OPERATOR");
352
352
  }
353
- // ── 004-event-processor-inbox ─────────────────────────────────────────────────
354
- /** A setting 004 renamed. It is REFUSED, not read: see `processorHttpPort`. */
353
+ /**
354
+ * Opt-in READ-ONLY registry mode (the installer always sets this; the published app image and the
355
+ * real-estate operator deployment must NOT).
356
+ *
357
+ * In this mode the registry never claims or signs with any identity, submits nothing, and its role
358
+ * decision can never yield `owner`. Owned+open+operator-matches reads back `reader`; an unclaimed
359
+ * channel drains nothing and `wait`s; an owner different from the pinned operator or a non-open
360
+ * write policy is `unusable`. Without this flag the existing owner path is byte-for-byte unchanged.
361
+ */
362
+ export function registryReadOnly() {
363
+ return optionalEnv("REGISTRY_READ_ONLY") === "1";
364
+ }
365
+ // ── 004-event-processor-inbox / 177-managed-processor-shell ───────────────────
366
+ /** A retired setting that the managed shell no longer reads. A non-empty value is REFUSED, not
367
+ * read, so a stale deployment cannot silently diverge (processing with no node control, or
368
+ * serving nothing). See `refuseRetiredSettings`. */
355
369
  export class RenamedSettingError extends Error {
356
370
  }
371
+ /** `registryStatePath(target)` moves here beside [[ensureStateDir]] — the shell owns the store
372
+ * path, and both `src/service.ts` and `scripts/recover-owner.ts` need it. */
373
+ export function registryStatePath(target) {
374
+ return join(target.stateDir, "registry.db");
375
+ }
357
376
  /**
358
- * THE SWITCH BETWEEN THE APP'S TWO SHAPES, and the event processor's read-server port.
377
+ * FR-031 — the managed shell reads ONLY the binding (`EVOLVER_CHAIN_ID`, `APP_NODE_ID`) from the
378
+ * environment. Every other retired Meerkat setting is a fixed container default or an explicit
379
+ * option, and a non-empty value must REFUSE startup. A compose `${X:-}` renders present-and-EMPTY,
380
+ * so an empty string is NOT a refusal (it is "unset"); only a non-empty value refuses. The message
381
+ * names the offending variable.
359
382
  *
360
- * `> 0` is the SERVICE shape: the event processor with the registry module, serving its routes
361
- * on this port, until stopped. Unset or `0` is the DEMO shape: the event processor with the demo
362
- * module, for as long as the template's steps take. Both shapes have exactly one consumer of
363
- * the node's stream — the processor — and both keep what nobody handles in the inbox.
364
- *
365
- * ⚠️ IT STAYS ONE SWITCH BECAUSE THE NODE READS IT TOO. `node-join.sh` decides from the same
366
- * value whether the node subscribes to `REGISTRY_CHANNEL`, and phase M of the command suite
367
- * fails the build if the two are keyed on anything else. A second "which modules" setting could
368
- * disagree with the node's channel list, and the registry would then index nothing (research R1).
369
- *
370
- * `0` = no read server, mirroring `https.port`'s convention in the node — the port IS the switch,
371
- * so there is no second `enabled` flag.
372
- *
373
- * ⚠️ `REGISTRY_HTTP_PORT`, its name before 004, is REFUSED whenever it is set — not read, not
374
- * aliased. Ignoring it would be worse than either: a registry deployment whose environment still
375
- * says `REGISTRY_HTTP_PORT=8090` would start as the template DEMO, drain its node with the wrong
376
- * module and serve nothing, and say so nowhere. The refusal names the fix.
383
+ * `EVOLVER_MANAGED_NODE_CONTAINER` is evolver-mcp's node container_name and is deliberately NOT on
384
+ * this list.
377
385
  */
378
- export function processorHttpPort() {
379
- const old = optionalEnv("REGISTRY_HTTP_PORT");
380
- if (old !== undefined)
381
- throw new RenamedSettingError(`REGISTRY_HTTP_PORT is set (${old}), but it was renamed PROCESSOR_HTTP_PORT and is no longer read.\n` +
382
- ` Set PROCESSOR_HTTP_PORT=${old} and remove REGISTRY_HTTP_PORT. Left as it is, this deployment\n` +
383
- ` would start as the template demo instead of the registry.`);
384
- const port = Number(optionalEnv("PROCESSOR_HTTP_PORT") ?? 0);
385
- if (!Number.isSafeInteger(port) || port < 0 || port > 65535)
386
- throw new TargetError("invalid PROCESSOR_HTTP_PORT");
387
- return port;
386
+ export function refuseRetiredSettings(env = process.env) {
387
+ for (const name of RETIRED_SETTINGS) {
388
+ const value = env[name];
389
+ if (value !== undefined && value !== "") {
390
+ throw new RenamedSettingError(`${name} is set (${value}), but it is a retired Meerkat setting and is no longer read:\n` +
391
+ ` the managed processor shell fixes every setting as a container default or an explicit\n` +
392
+ ` option, reading only EVOLVER_CHAIN_ID / APP_NODE_ID from the environment. Unset ${name}\n` +
393
+ ` and start again; reading it would let a stale deployment silently diverge.`);
394
+ }
395
+ }
388
396
  }
397
+ const RETIRED_SETTINGS = [
398
+ "EVOLVER_PROCESSOR_CONTROL_SOCKET",
399
+ "PROCESSOR_HTTP_PORT",
400
+ "REGISTRY_HTTP_PORT",
401
+ "PROCESSOR_READ_HOST",
402
+ "EVOLVER_SUBSCRIPTIONS_FILE",
403
+ ];
389
404
  /** The lease ceiling, in ROUNDS, measured from the round an announcement is ordered.
390
405
  *
391
406
  * ⚠️ Rounds, never wall-clock: devnet commits on a ~600 ms floor when busy and a 300-second
@@ -2,7 +2,7 @@
2
2
  // the SDK. Signing keys and chain provisioning remain in this parent, never worker options.
3
3
  import { isValidNodeId } from "@mcsherrylabs/evolver-client";
4
4
  import { registryChannel, registryChannelOperator, registryContentUrl, registryDenyList, registryMaxLeaseRounds, } from "../env.js";
5
- import { provisionRegistryChannel } from "./provision.js";
5
+ import { provisionRegistryChannel, provisionRegistryChannelReadOnly } from "./provision.js";
6
6
  import { REGISTRY_MODULE_NAME } from "./worker-module.js";
7
7
  export function registryModule(args) {
8
8
  if (!isValidNodeId(args.operator))
@@ -13,6 +13,7 @@ export function registryModule(args) {
13
13
  maxLeaseRounds: registryMaxLeaseRounds(), deny: [...registryDenyList()],
14
14
  chainUrl: args.chainUrl, contentUrl: registryContentUrl(),
15
15
  };
16
+ const pinnedOperator = registryChannelOperator();
16
17
  return {
17
18
  // Production starts emitted JS. No tsx worker remapping, test factory or remote discovery.
18
19
  worker: { url: new URL("./worker-module.js", import.meta.url).href, options },
@@ -20,13 +21,21 @@ export function registryModule(args) {
20
21
  name: REGISTRY_MODULE_NAME, channels: [channel],
21
22
  async setup(context) {
22
23
  await args.beforeProvision?.();
24
+ if (args.readOnly) {
25
+ const provisioned = await provisionRegistryChannelReadOnly({
26
+ chainAgreedUrl: args.chainAgreedUrl, channel, nodeIdentity: args.nodeIdentity,
27
+ say: args.say, operator: pinnedOperator ?? args.operator,
28
+ });
29
+ args.say(` registry ${provisioned.role}: ${channel}`);
30
+ return;
31
+ }
23
32
  const { sender, signer } = await args.identity();
24
33
  if (sender.nodeId !== args.operator)
25
34
  throw new Error("registry signing identity differs from fixed worker operator");
26
35
  const provisioned = await provisionRegistryChannel({
27
36
  node: args.node, chainUrl: args.chainUrl, chainAgreedUrl: args.chainAgreedUrl,
28
37
  channel, owner: sender, signer, nodeIdentity: args.nodeIdentity,
29
- say: args.say, operator: registryChannelOperator(),
38
+ say: args.say, operator: pinnedOperator,
30
39
  pace: async () => { await context.pace(); },
31
40
  });
32
41
  args.say(` registry ${provisioned.role}: ${channel}`);
@@ -29,7 +29,28 @@
29
29
  import { buildSignedTx, claimBody, updateChannelWritePolicyBody, LEDGER } from "@mcsherrylabs/evolver-client";
30
30
  import { channelTerms, submitAndConfirm, updateChannelDaBody, } from "../announcements.js";
31
31
  export function decideRegistryRole(args) {
32
- const { terms, self, operator } = args;
32
+ const { terms, self, operator, readOnly } = args;
33
+ // READ-ONLY: the installer's registry never claims or signs with any identity. `self` is the
34
+ // PINNED operator name, not this deployment's (nonexistent) identity, and it must NEVER yield
35
+ // `owner` — a read-only indexer submitting a Claim for a channel someone else owns poisons the
36
+ // chain's tx queue (evolver#85). Decision folds entirely onto the pinned operator.
37
+ if (readOnly) {
38
+ if (terms.owners.length === 0) {
39
+ return { kind: "waiting", why: `'${terms.ledgerId}' is UNCLAIMED and this read-only registry will not claim it (owned ` +
40
+ `by '${operator ?? "(no operator pinned)"}', not by this deployment). Draining the cursor ` +
41
+ `and indexing nothing until the operator provisions it.` };
42
+ }
43
+ const holder = terms.owners.map((o) => o.name).join(", ");
44
+ if (operator && !terms.owners.some((o) => o.name === operator))
45
+ return { kind: "unusable", why: `'${terms.ledgerId}' is owned by ${holder}, but REGISTRY_CHANNEL_OPERATOR pins ` +
46
+ `'${operator}'. Either the operator changed — update the pin — or this name was taken by ` +
47
+ `somebody else. Refusing to index a channel whose terms are set by a party this deployment ` +
48
+ `was not told to trust.` };
49
+ if (terms.writePolicy !== "open")
50
+ return { kind: "unusable", why: `'${terms.ledgerId}' is owned by ${holder} and its write policy is ` +
51
+ `'${terms.writePolicy}', not 'open'. Every announcement would be a permanent miss.` };
52
+ return { kind: "reader", holder };
53
+ }
33
54
  // ALREADY OURS — see "keeping is not acquiring" above. Note this is the IDENTITY name,
34
55
  // matching what the claim/converge path below can actually sign for: a channel owned by a
35
56
  // group we happen to belong to is deliberately NOT treated as ours, because this app holds
@@ -178,3 +199,27 @@ export async function provisionRegistryChannel(args) {
178
199
  say(` '${channel}' already has the terms this registry needs — nothing to submit`);
179
200
  return { role: "owner", channel, member: nodeIdentity, writes };
180
201
  }
202
+ /**
203
+ * READ-ONLY provisioning: decide the role from the chain-agreed terms and NEVER submit, claim or
204
+ * sign. Used only by the installer's registry (`REGISTRY_READ_ONLY=1`), where no identity exists to
205
+ * sign with and the pinned operator (`REGISTRY_CHANNEL_OPERATOR`) is the only authority trusted.
206
+ */
207
+ export async function provisionRegistryChannelReadOnly(args) {
208
+ const { chainAgreedUrl, channel, nodeIdentity, operator, say } = args;
209
+ const read = await channelTerms(chainAgreedUrl, channel);
210
+ if (!read.ok)
211
+ throw new Error(`could not read the terms of '${channel}' from ${chainAgreedUrl}: ${read.why}\n` +
212
+ ` Refusing to guess — an unreadable answer is "not yet", never a licence to index unverified terms.`);
213
+ const role = decideRegistryRole({ terms: read.terms, self: operator ?? "", operator, readOnly: true });
214
+ if (role.kind === "unusable")
215
+ throw new Error(role.why);
216
+ if (role.kind === "waiting") {
217
+ say(` ⚠ ${role.why}`);
218
+ return { role: "waiting", channel, member: nodeIdentity, writes: 0 };
219
+ }
220
+ if (role.kind !== "reader")
221
+ throw new Error(`read-only registry resolved an impossible owner role — refusing to index`);
222
+ say(` '${channel}' is owned by ${role.holder} — running as a READ-ONLY reader`);
223
+ say(` nothing will be claimed, provisioned or submitted here`);
224
+ return { role: "reader", channel, member: nodeIdentity, writes: 0 };
225
+ }
@@ -0,0 +1,151 @@
1
+ // The registry service: application configuration + module registration only (FR-004, SC-001).
2
+ // All generic cursor/storage/config/control/lifecycle ownership lives in the SDK managed shell
3
+ // (`startManagedProcessor`); this file wires the registry module, its routes, the state path, the
4
+ // own-node client and the operator identity to that shell. No processor-composition code of its
5
+ // own remains here.
6
+ import { fileURLToPath } from "node:url";
7
+ import { readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { isValidNodeId, NodeClient } from "@mcsherrylabs/evolver-client";
10
+ import { deadlineFetch, MANAGED_PROCESSOR_DEFAULTS, startManagedProcessor, } from "@mcsherrylabs/evolver-client/processor";
11
+ import { ensureStateDir, refuseRetiredSettings, registryChannelOperator, registryReadOnly, registryStatePath, ROOT, } from "./env.js";
12
+ import * as store from "./store.js";
13
+ import { identityPrefix } from "../scripts/versions.mjs";
14
+ import { CHAIN_TIMEOUT_MS } from "./chainfetch.js";
15
+ import { registryModule, registryReadRoutes } from "./registry/index.js";
16
+ /** The operator identity name the registry claims/signs as. Resolved BEFORE any network: from
17
+ * saved state, else a freshly derived name, always carrying the per-target suffix on a shared
18
+ * chain so two deployments never collide on one name. */
19
+ function nameFor(target, prefix, suffix) {
20
+ return target.chainIsEphemeral ? `${prefix}A` : `${prefix}A${suffix()}`;
21
+ }
22
+ function devnetSuffixFor(target, prefix) {
23
+ let cached = null;
24
+ return () => {
25
+ if (cached)
26
+ return cached;
27
+ for (const { nodeId } of store.summary(target)) {
28
+ const m = new RegExp(`^${prefix}A(\\d+)$`).exec(nodeId);
29
+ if (m)
30
+ return (cached = m[1]);
31
+ }
32
+ return (cached = String(Date.now()).slice(-6));
33
+ };
34
+ }
35
+ /**
36
+ * The registry service: refuse retired settings, build the own-node client (never the target's node URL,
37
+ * FR-012), claim/resolve the operator identity, register the registry module with the SDK shell,
38
+ * and start it. Everything the shell owns (owner lock, banner, control socket, stale-socket
39
+ * recovery, node-control restart, signals) is the SDK's.
40
+ */
41
+ export async function startRegistryService(options) {
42
+ refuseRetiredSettings(); // FR-031
43
+ const nodeUrl = options.shell?.nodeUrl ?? MANAGED_PROCESSOR_DEFAULTS.nodeUrl; // FR-012: never the target's node URL
44
+ const node = new NodeClient({ baseUrl: nodeUrl, fetch: deadlineFetch(CHAIN_TIMEOUT_MS) });
45
+ // ── operator identity + read-only mode (ported from the old service branch of src/app.ts) ──
46
+ const readOnly = registryReadOnly();
47
+ // READ-ONLY: the installer's registry never claims or signs with any identity. The operator is
48
+ // the PINNED channel operator; no sender identity is ever loaded or claimed, so the identity()
49
+ // claim path is never reached in this mode. A read-only registry without a valid pinned operator
50
+ // has nobody to trust the channel's terms to, so it refuses at startup rather than proceeding
51
+ // owner-less.
52
+ const pinnedOperator = registryChannelOperator();
53
+ if (readOnly && !pinnedOperator)
54
+ throw new Error("REGISTRY_READ_ONLY=1 requires a valid REGISTRY_CHANNEL_OPERATOR naming the identity " +
55
+ "that owns the index channel; the installer's rendered compose always sets both.");
56
+ // This app's own name, from package.json — never a literal, so a rename cannot leave a stale one.
57
+ const appName = (() => {
58
+ try {
59
+ return JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).name ?? "app";
60
+ }
61
+ catch {
62
+ return "app";
63
+ }
64
+ })();
65
+ const prefix = identityPrefix(appName);
66
+ const suffix = devnetSuffixFor(options.target, prefix);
67
+ const operator = readOnly ? pinnedOperator : store.get(options.target, "sender")?.nodeId ?? nameFor(options.target, prefix, suffix);
68
+ const nodeIdentity = process.env.APP_NODE_ID?.trim() ?? "";
69
+ const faucet = new NodeClient({ baseUrl: options.target.faucetUrl, fetch: deadlineFetch(CHAIN_TIMEOUT_MS) });
70
+ /** Load, or claim and persist, the sender identity. Only the "sender" role is ever used by the
71
+ * registry. Reconciles against the chain (the faucet's committed view) rather than trusting
72
+ * state — see the extensive note that lived in the old app.ts service branch. */
73
+ const identity = async () => {
74
+ if (readOnly)
75
+ throw new Error("read-only registry must never claim or sign with any identity");
76
+ const existing = store.get(options.target, "sender");
77
+ if (existing) {
78
+ const onChain = await faucet.getIdentity(existing.nodeId);
79
+ if (onChain !== null)
80
+ return { sender: existing, signer: store.signerFor(existing) };
81
+ options.say(` sender: ${existing.nodeId} is in state but not on chain — claiming it now`);
82
+ const retry = await faucet.claimIdentity(existing);
83
+ if (retry.kind === "committed")
84
+ return { sender: existing, signer: store.signerFor(existing) };
85
+ if (retry.kind === "exists") {
86
+ throw new Error(`'${existing.nodeId}' is on chain with keys this app does not hold.\n` +
87
+ ` state/${options.target.name} is stale for 'sender'. npm run state:reset` +
88
+ (options.target.chainIsEphemeral ? ` (or npm run reset for chain + state)` : ``));
89
+ }
90
+ throw new Error(`re-claim of '${existing.nodeId}' failed: ${retry.error}` +
91
+ `${retry.message ? ` — ${retry.message}` : ""}`);
92
+ }
93
+ const name = nameFor(options.target, prefix, suffix);
94
+ if (!isValidNodeId(name)) {
95
+ throw new Error(`'${name}' is not a usable identity name — the node requires 1–50 ASCII ` +
96
+ `alphanumeric characters (letters and digits only).`);
97
+ }
98
+ const id = store.create(options.target, "sender", name);
99
+ const r = await faucet.claimIdentity(id);
100
+ switch (r.kind) {
101
+ case "committed": return { sender: id, signer: store.signerFor(id) };
102
+ case "exists":
103
+ throw new Error(`'${name}' already exists on chain and these keys are not its keys.\n` +
104
+ (!options.target.chainIsEphemeral
105
+ ? ` this target's state was reset but the name was reused — npm run state:reset, then retry`
106
+ : process.env.EVOLVER_IN_CONTAINER === "1"
107
+ ? ` the chain outlived the app's state. Discard both together:\n` +
108
+ ` docker compose down -v (or: docker compose -f oci://<ref> down -v)`
109
+ : ` the local chain outlived its state: npm run reset (chain + state together)`));
110
+ case "failed":
111
+ if (r.error === "digest_mismatch") {
112
+ throw new Error(`CLAIM RECIPE DRIFT — ${r.message}\n` +
113
+ ` Nothing was signed. The node and @mcsherrylabs/evolver-client disagree on the\n` +
114
+ ` proof-of-possession digest, which means their versions do not match.`);
115
+ }
116
+ throw new Error(`claim of '${name}' failed: ${r.error}${r.message ? ` — ${r.message}` : ""}`);
117
+ }
118
+ };
119
+ /** Wait until this own node can actually write (stopped bulk-syncing) before provisioning. */
120
+ const beforeProvision = async () => {
121
+ const deadline = Date.now() + 180_000;
122
+ let last = await node.currentRound();
123
+ let announced = false;
124
+ while (Date.now() < deadline) {
125
+ await new Promise((r) => setTimeout(r, 500));
126
+ const now = await node.currentRound();
127
+ const advanced = now - last;
128
+ last = now;
129
+ if (advanced < 5)
130
+ return;
131
+ if (!announced) {
132
+ options.say(` this node is still catching up (round ${now}, +${advanced}/500ms) — waiting`);
133
+ announced = true;
134
+ }
135
+ }
136
+ throw new Error(`this node is still bulk-syncing after 3 minutes (round ${last}).\n` +
137
+ ` A submit would be accepted and then sit unrouted, so nothing is sent.`);
138
+ };
139
+ const module = registryModule({
140
+ chain: options.target.name, node, chainUrl: nodeUrl,
141
+ chainAgreedUrl: options.target.faucetUrl, operator, nodeIdentity, readOnly, identity,
142
+ beforeProvision, say: options.say,
143
+ });
144
+ ensureStateDir(options.target);
145
+ return startManagedProcessor({ ...options.shell,
146
+ recovery: { service: "registry", command: `node ${fileURLToPath(new URL("../scripts/recover-owner.js", import.meta.url))} --force` },
147
+ paths: { ...options.shell?.paths, state: registryStatePath(options.target) },
148
+ storeModules: [module.worker], lifecycle: [module.lifecycle],
149
+ routes: runtime => registryReadRoutes(runtime, async () => String(await node.currentRound())),
150
+ say: options.say });
151
+ }
@@ -1,11 +1,13 @@
1
+ export type Mode = 'devnet' | 'local';
1
2
  export interface Installation {
2
3
  version: 1;
3
4
  uid: number;
4
5
  id: string;
5
6
  dir: string;
6
7
  project: string;
8
+ mode: Mode;
7
9
  nodeId: string;
8
- committeeId: string;
10
+ committeeId?: string;
9
11
  identity: string;
10
12
  registryOperator: string;
11
13
  channel: string;
@@ -21,17 +23,20 @@ export interface Installation {
21
23
  }
22
24
  export interface InstallerDeps {
23
25
  command(command: string, args: string[], env?: NodeJS.ProcessEnv, cwd?: string): Promise<string>;
24
- committeeReady(state: Installation, env: NodeJS.ProcessEnv): Promise<void>;
26
+ committeeReady?(state: Installation, env: NodeJS.ProcessEnv): Promise<void>;
25
27
  nodeBinding(state: Installation, env: NodeJS.ProcessEnv): Promise<{
26
28
  chainId: string;
27
29
  nodeId: string;
28
30
  }>;
29
31
  registryReady(state: Installation, env: NodeJS.ProcessEnv): Promise<void>;
30
32
  bootstrap(state: Installation, env: NodeJS.ProcessEnv): Promise<void>;
33
+ claimChannel?(state: Installation, env: NodeJS.ProcessEnv): Promise<void>;
31
34
  }
32
35
  export declare function managedDirectory(env?: NodeJS.ProcessEnv): string;
33
- export declare const managedConfigPath: (env?: NodeJS.ProcessEnv) => string;
34
- export declare function pinnedRegistration(version: string): {
36
+ /** Per-mode state directory. The 0.8.0 path `<state>/meerkat/` (installation.json) is never used. */
37
+ export declare function modeDirectory(mode: Mode, env?: NodeJS.ProcessEnv): string;
38
+ export declare const managedConfigPath: (mode?: Mode, env?: NodeJS.ProcessEnv) => string;
39
+ export declare function pinnedRegistration(version: string, mode?: Mode): {
35
40
  mcpServers: {
36
41
  meerkat: {
37
42
  command: string;
@@ -40,6 +45,152 @@ export declare function pinnedRegistration(version: string): {
40
45
  };
41
46
  };
42
47
  export declare function renderDeployment(state: Installation): {
48
+ name: string;
49
+ services: {
50
+ node: {
51
+ image: string;
52
+ labels: {
53
+ "io.meerkat.installation": string;
54
+ };
55
+ container_name: string;
56
+ entrypoint: string[];
57
+ build: {
58
+ context: string;
59
+ dockerfile: string;
60
+ labels: {
61
+ "io.meerkat.installation": string;
62
+ };
63
+ args: {
64
+ EVOLVER_NODE_IMAGE: string;
65
+ };
66
+ };
67
+ environment: {
68
+ APP_NODE_ID: string;
69
+ DEVNET_API: string;
70
+ EVOLVER_UPSTREAM_NODE_ID: string;
71
+ EVOLVER_DA_PEERS: string;
72
+ EVOLVER_ALLOWED_OUTBOUND: string;
73
+ EVOLVER_EXECUTION_ENABLED: string;
74
+ EVOLVER_NODE_PORT: string;
75
+ EVOLVER_NODE_CONFIG?: undefined;
76
+ CLAIM_TIMEOUT?: undefined;
77
+ CLAIM_WATCH_SECS?: undefined;
78
+ };
79
+ group_add: string[];
80
+ volumes: string[];
81
+ restart: string;
82
+ };
83
+ registry: {
84
+ image: string;
85
+ labels: {
86
+ "io.meerkat.installation": string;
87
+ };
88
+ container_name: string;
89
+ build: {
90
+ context: string;
91
+ dockerfile: string;
92
+ labels: {
93
+ "io.meerkat.installation": string;
94
+ };
95
+ args: {
96
+ EVOLVER_PROCESSOR_IMAGE: string;
97
+ };
98
+ };
99
+ user: string;
100
+ group_add: string[];
101
+ environment: {
102
+ EVOLVER_FAUCET_URL: string;
103
+ EVOLVER_CHAIN_ID: string;
104
+ APP_NODE_ID: string;
105
+ REGISTRY_CHANNEL: string;
106
+ REGISTRY_CHANNEL_OPERATOR: string;
107
+ REGISTRY_READ_ONLY: string;
108
+ REGISTRY_CONTENT_URL: string;
109
+ EVOLVER_DEVNET_URL?: string | undefined;
110
+ EVOLVER_TARGET: string;
111
+ EVOLVER_NODE: string;
112
+ };
113
+ volumes: string[];
114
+ restart: string;
115
+ };
116
+ 'subscriptions-init': {
117
+ image: string;
118
+ labels: {
119
+ "io.meerkat.installation": string;
120
+ };
121
+ profiles: string[];
122
+ user: string;
123
+ environment: {
124
+ EVOLVER_INITIAL_CHANNELS: string;
125
+ EVOLVER_OPERATOR_UID: string;
126
+ EVOLVER_OPERATOR_GID: string;
127
+ EVOLVER_FAUCET_URL: string;
128
+ EVOLVER_CHAIN_ID: string;
129
+ APP_NODE_ID: string;
130
+ REGISTRY_CHANNEL: string;
131
+ REGISTRY_CHANNEL_OPERATOR: string;
132
+ REGISTRY_READ_ONLY: string;
133
+ REGISTRY_CONTENT_URL: string;
134
+ EVOLVER_DEVNET_URL?: string | undefined;
135
+ EVOLVER_TARGET: string;
136
+ EVOLVER_NODE: string;
137
+ };
138
+ entrypoint: string[];
139
+ command: string[];
140
+ volumes: string[];
141
+ };
142
+ committee?: undefined;
143
+ };
144
+ volumes: {
145
+ 'node-data': {
146
+ name: string;
147
+ labels: {
148
+ "io.meerkat.installation": string;
149
+ };
150
+ };
151
+ 'app-state': {
152
+ name: string;
153
+ labels: {
154
+ "io.meerkat.installation": string;
155
+ };
156
+ };
157
+ subscriptions: {
158
+ name: string;
159
+ labels: {
160
+ "io.meerkat.installation": string;
161
+ };
162
+ };
163
+ 'agent-state': {
164
+ name: string;
165
+ labels: {
166
+ "io.meerkat.installation": string;
167
+ };
168
+ };
169
+ 'node-control': {
170
+ name: string;
171
+ labels: {
172
+ "io.meerkat.installation": string;
173
+ };
174
+ };
175
+ control: {
176
+ name: string;
177
+ labels: {
178
+ "io.meerkat.installation": string;
179
+ };
180
+ };
181
+ 'committee-data'?: undefined;
182
+ genesis?: undefined;
183
+ };
184
+ networks: {
185
+ default: {
186
+ name: string;
187
+ labels: {
188
+ "io.meerkat.installation": string;
189
+ };
190
+ internal?: undefined;
191
+ };
192
+ };
193
+ } | {
43
194
  name: string;
44
195
  services: {
45
196
  committee: {
@@ -60,7 +211,7 @@ export declare function renderDeployment(state: Installation): {
60
211
  };
61
212
  entrypoint: string[];
62
213
  environment: {
63
- NODE_ID: string;
214
+ NODE_ID: string | undefined;
64
215
  PUBLIC_HOST: string;
65
216
  };
66
217
  group_add: string[];
@@ -87,12 +238,14 @@ export declare function renderDeployment(state: Installation): {
87
238
  environment: {
88
239
  APP_NODE_ID: string;
89
240
  DEVNET_API: string;
90
- EVOLVER_UPSTREAM_NODE_ID: string;
241
+ EVOLVER_UPSTREAM_NODE_ID: string | undefined;
91
242
  EVOLVER_ALLOWED_OUTBOUND: string;
92
243
  EVOLVER_EXECUTION_ENABLED: string;
93
244
  EVOLVER_NODE_PORT: string;
245
+ EVOLVER_NODE_CONFIG: string;
94
246
  CLAIM_TIMEOUT: string;
95
247
  CLAIM_WATCH_SECS: string;
248
+ EVOLVER_DA_PEERS?: undefined;
96
249
  };
97
250
  group_add: string[];
98
251
  volumes: string[];
@@ -110,20 +263,23 @@ export declare function renderDeployment(state: Installation): {
110
263
  labels: {
111
264
  "io.meerkat.installation": string;
112
265
  };
266
+ args: {
267
+ EVOLVER_PROCESSOR_IMAGE: string;
268
+ };
113
269
  };
114
270
  user: string;
271
+ group_add: string[];
115
272
  environment: {
116
- EVOLVER_TARGET: string;
117
- EVOLVER_NODE: string;
118
273
  EVOLVER_FAUCET_URL: string;
119
274
  EVOLVER_CHAIN_ID: string;
120
275
  APP_NODE_ID: string;
121
- EVOLVER_SUBSCRIPTIONS_FILE: string;
122
276
  REGISTRY_CHANNEL: string;
123
277
  REGISTRY_CHANNEL_OPERATOR: string;
278
+ REGISTRY_READ_ONLY: string;
124
279
  REGISTRY_CONTENT_URL: string;
125
- PROCESSOR_HTTP_PORT: string;
126
- PROCESSOR_READ_HOST: string;
280
+ EVOLVER_DEVNET_URL?: string | undefined;
281
+ EVOLVER_TARGET: string;
282
+ EVOLVER_NODE: string;
127
283
  };
128
284
  volumes: string[];
129
285
  restart: string;
@@ -139,17 +295,16 @@ export declare function renderDeployment(state: Installation): {
139
295
  EVOLVER_INITIAL_CHANNELS: string;
140
296
  EVOLVER_OPERATOR_UID: string;
141
297
  EVOLVER_OPERATOR_GID: string;
142
- EVOLVER_TARGET: string;
143
- EVOLVER_NODE: string;
144
298
  EVOLVER_FAUCET_URL: string;
145
299
  EVOLVER_CHAIN_ID: string;
146
300
  APP_NODE_ID: string;
147
- EVOLVER_SUBSCRIPTIONS_FILE: string;
148
301
  REGISTRY_CHANNEL: string;
149
302
  REGISTRY_CHANNEL_OPERATOR: string;
303
+ REGISTRY_READ_ONLY: string;
150
304
  REGISTRY_CONTENT_URL: string;
151
- PROCESSOR_HTTP_PORT: string;
152
- PROCESSOR_READ_HOST: string;
305
+ EVOLVER_DEVNET_URL?: string | undefined;
306
+ EVOLVER_TARGET: string;
307
+ EVOLVER_NODE: string;
153
308
  };
154
309
  entrypoint: string[];
155
310
  command: string[];
@@ -193,6 +348,18 @@ export declare function renderDeployment(state: Installation): {
193
348
  "io.meerkat.installation": string;
194
349
  };
195
350
  };
351
+ 'node-control': {
352
+ name: string;
353
+ labels: {
354
+ "io.meerkat.installation": string;
355
+ };
356
+ };
357
+ control: {
358
+ name: string;
359
+ labels: {
360
+ "io.meerkat.installation": string;
361
+ };
362
+ };
196
363
  };
197
364
  networks: {
198
365
  default: {
@@ -205,11 +372,11 @@ export declare function renderDeployment(state: Installation): {
205
372
  };
206
373
  };
207
374
  export declare function operatorEnvironment(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
208
- export declare function managedInstallation(env?: NodeJS.ProcessEnv): Promise<Installation | undefined>;
375
+ export declare function managedInstallation(mode?: Mode, env?: NodeJS.ProcessEnv): Promise<Installation | undefined>;
209
376
  /** Protocol-only stdio bridge, one new facade/private child/session per harness connection.
210
377
  * Docker exec works even when daemon and harness share neither filesystem nor loopback. */
211
378
  export declare function serveManaged(state: Installation, env?: NodeJS.ProcessEnv): Promise<void>;
212
- export declare function installMeerkat(env?: NodeJS.ProcessEnv, deps?: InstallerDeps): Promise<{
379
+ export declare function installMeerkat(mode?: Mode, env?: NodeJS.ProcessEnv, deps?: InstallerDeps): Promise<{
213
380
  dir: string;
214
381
  configPath: string;
215
382
  registration: {
@@ -1 +1 @@
1
- {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,CAAC,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IACvG,QAAQ,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IACvI,cAAc,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;IAAC,KAAK,CAAC,EAAE,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACnH;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjG,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,WAAW,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvG,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AACD,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAI7E;AACD,eAAO,MAAM,iBAAiB,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,MAA0D,CAAC;AACpI,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM;;;;;;;EAGjD;AACD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsCnD;AA2BD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAK7E;AAwDD,wBAAsB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAOjH;AACD;2FAC2F;AAC3F,wBAAsB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC3G;AACD,wBAAsB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,IAAI,GAAE,aAAwB;;;;;;;;;;;GAiFxG"}
1
+ {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AA4CA,MAAM,MAAM,IAAI,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,CAAC,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAC9E,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAClG,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAC1E,cAAc,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;IAAC,KAAK,CAAC,EAAE,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACnH;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjG,cAAc,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,WAAW,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvG,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,YAAY,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3E;AACD,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAI7E;AACD,qGAAqG;AACrG,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAEtF;AACD,eAAO,MAAM,iBAAiB,GAAI,OAAM,IAAe,EAAE,MAAK,MAAM,CAAC,UAAwB,KAAG,MAC3C,CAAC;AAEtD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,IAAe;;;;;;;EAGxE;AAkBD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqEnD;AA+BD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAK7E;AAwED,wBAAsB,mBAAmB,CAAC,IAAI,GAAE,IAAe,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAOxI;AACD;2FAC2F;AAC3F,wBAAsB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC3G;AACD,wBAAsB,cAAc,CAAC,IAAI,GAAE,IAAe,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,IAAI,GAAE,aAAwB;;;;;;;;;;;GAgG/H"}