@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.
- package/README.md +34 -10
- package/dist/installer/committee-init.sh +17 -33
- package/dist/installer/committee.conf +12 -34
- package/dist/installer/genesis.devnet.conf +92 -0
- package/dist/installer/node-follower.conf +232 -0
- package/dist/installer/node-join.sh +15 -1
- package/dist/installer/node.conf +4 -231
- package/dist/installer/node.local.conf +8 -0
- package/dist/installer/runtime.json +1 -1
- package/dist/installer-runtime/dist/scripts/recover-owner.js +7 -9
- package/dist/installer-runtime/dist/scripts/subscriptions-init.js +3 -4
- package/dist/installer-runtime/dist/scripts/versions.mjs +5 -10
- package/dist/installer-runtime/dist/src/announcements.js +1 -289
- package/dist/installer-runtime/dist/src/app.js +12 -535
- package/dist/installer-runtime/dist/src/chainfetch.js +0 -64
- package/dist/installer-runtime/dist/src/env.js +47 -32
- package/dist/installer-runtime/dist/src/registry/index.js +11 -2
- package/dist/installer-runtime/dist/src/registry/provision.js +46 -1
- package/dist/installer-runtime/dist/src/service.js +151 -0
- package/dist/installer.d.ts +185 -18
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +161 -52
- package/dist/installer.js.map +1 -1
- package/dist/mcp-announce.d.ts +18 -0
- package/dist/mcp-announce.d.ts.map +1 -1
- package/dist/mcp-announce.js +18 -4
- package/dist/mcp-announce.js.map +1 -1
- package/dist/mcp-cli.d.ts.map +1 -1
- package/dist/mcp-cli.js +28 -18
- package/dist/mcp-cli.js.map +1 -1
- package/dist/mcp-config.d.ts +1 -0
- package/dist/mcp-config.d.ts.map +1 -1
- package/dist/mcp-config.js +2 -1
- package/dist/mcp-config.js.map +1 -1
- package/dist/mcp-upstream.d.ts +1 -1
- package/dist/mcp-upstream.d.ts.map +1 -1
- package/dist/mcp-upstream.js +7 -2
- package/dist/mcp-upstream.js.map +1 -1
- package/dist/tools.js +4 -4
- package/dist/tools.js.map +1 -1
- package/installer/Node.Dockerfile +5 -0
- package/installer/Registry.Dockerfile +34 -9
- package/installer/check-single-sdk.mjs +151 -0
- package/installer/runtime-control.mjs +179 -16
- package/package.json +5 -5
- package/skills/channel-subscription/SKILL.md +3 -3
- 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:
|
|
90
|
-
*
|
|
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
|
-
|
|
354
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
361
|
-
*
|
|
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
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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:
|
|
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
|
+
}
|
package/dist/installer.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
34
|
-
export declare function
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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: {
|
package/dist/installer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"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"}
|