@cello-protocol/daemon 0.0.24 → 0.0.25
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/dist/bin/cello-daemon.js +11 -64
- package/dist/bin/cello-daemon.js.map +1 -1
- package/dist/bundled-consortium-manifest.d.ts +45 -0
- package/dist/bundled-consortium-manifest.d.ts.map +1 -0
- package/dist/bundled-consortium-manifest.js +79 -0
- package/dist/bundled-consortium-manifest.js.map +1 -0
- package/dist/file-manifest-provider.d.ts +19 -0
- package/dist/file-manifest-provider.d.ts.map +1 -1
- package/dist/file-manifest-provider.js +32 -0
- package/dist/file-manifest-provider.js.map +1 -1
- package/dist/manifest-deps.d.ts +36 -0
- package/dist/manifest-deps.d.ts.map +1 -0
- package/dist/manifest-deps.js +111 -0
- package/dist/manifest-deps.js.map +1 -0
- package/package.json +3 -3
package/dist/bin/cello-daemon.js
CHANGED
|
@@ -13,9 +13,7 @@ import { homedir } from "node:os";
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { startDaemon } from "../daemon.js";
|
|
15
15
|
import { createDirectoryEndpointResolver } from "../directory-bootstrap.js";
|
|
16
|
-
import {
|
|
17
|
-
import { RandomizedPollScheduler } from "../manifest-poll-scheduler.js";
|
|
18
|
-
import { ManifestDirectoryChallengeVerifier } from "@cello-protocol/transport";
|
|
16
|
+
import { buildManifestDeps } from "../manifest-deps.js";
|
|
19
17
|
const MAX_CONNECTIONS = 16;
|
|
20
18
|
// Composition root: stdout JSON logger
|
|
21
19
|
const logger = {
|
|
@@ -40,61 +38,6 @@ const celloDir = process.env.CELLO_DIR || join(homedir(), ".cello");
|
|
|
40
38
|
const socketPath = join(celloDir, "daemon.sock");
|
|
41
39
|
const lockFilePath = join(celloDir, "daemon.lock");
|
|
42
40
|
const version = process.env.CELLO_VERSION || "0.0.1";
|
|
43
|
-
/**
|
|
44
|
-
* Build the optional consortium-manifest deps from the environment (M7 J-AUTH).
|
|
45
|
-
*
|
|
46
|
-
* CELLO_CONSORTIUM_MANIFEST absolute path to the manifest JSON
|
|
47
|
-
* CELLO_CONSORTIUM_ROOT_KEYS comma-separated officer root pubkeys (hex)
|
|
48
|
-
* CELLO_CONSORTIUM_THRESHOLD minimum officer signatures (integer)
|
|
49
|
-
*
|
|
50
|
-
* When the manifest path is unset, returns {} — step-6 stays off (M6 compat).
|
|
51
|
-
* One FileManifestProvider instance is shared between startDaemon's loadAndVerify
|
|
52
|
-
* call and the ManifestDirectoryChallengeVerifier, so step-6 reads the same cached,
|
|
53
|
-
* verified manifest.
|
|
54
|
-
*/
|
|
55
|
-
function buildManifestDeps(logger) {
|
|
56
|
-
const manifestPath = process.env.CELLO_CONSORTIUM_MANIFEST;
|
|
57
|
-
if (!manifestPath)
|
|
58
|
-
return {};
|
|
59
|
-
const rootKeysRaw = process.env.CELLO_CONSORTIUM_ROOT_KEYS ?? "";
|
|
60
|
-
const manifestRootKeys = rootKeysRaw.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
|
|
61
|
-
const manifestThreshold = Number.parseInt(process.env.CELLO_CONSORTIUM_THRESHOLD ?? "", 10);
|
|
62
|
-
if (manifestRootKeys.length === 0 || Number.isNaN(manifestThreshold)) {
|
|
63
|
-
throw new Error("CELLO_CONSORTIUM_MANIFEST is set but CELLO_CONSORTIUM_ROOT_KEYS / CELLO_CONSORTIUM_THRESHOLD are missing or invalid");
|
|
64
|
-
}
|
|
65
|
-
const manifestProvider = new FileManifestProvider({ path: manifestPath });
|
|
66
|
-
const challengeVerifier = new ManifestDirectoryChallengeVerifier(manifestProvider);
|
|
67
|
-
// Anti-rollback (DOD-AUTH-2 / TUF): the last-verified manifest version is persisted to refuse a
|
|
68
|
-
// manifest whose version regressed across restarts. PERSIST-002 (AC-008): this now lives in the
|
|
69
|
-
// encrypted manifest_state table (a manifest-version.json file is no longer written) — startDaemon
|
|
70
|
-
// constructs the DB-backed store itself after opening the DB, so the bin injects nothing here.
|
|
71
|
-
// DOD-AUTH-2: background manifest poll. The directory is re-polled on a randomized
|
|
72
|
-
// 6–12h interval (thundering-herd avoidance) and a newer signed manifest is adopted.
|
|
73
|
-
// The interval is env-injectable so the live binary test can poll sub-second instead
|
|
74
|
-
// of waiting hours; production leaves these unset → the 6–12h default window.
|
|
75
|
-
// Both-or-neither, positive, min <= max — a partial/invalid override fails LOUDLY
|
|
76
|
-
// rather than silently reverting to 6–12h or producing a negative (tight-loop) delay.
|
|
77
|
-
const rawPollMin = process.env.CELLO_MANIFEST_POLL_MIN_MS;
|
|
78
|
-
const rawPollMax = process.env.CELLO_MANIFEST_POLL_MAX_MS;
|
|
79
|
-
let pollOpts;
|
|
80
|
-
if (rawPollMin !== undefined || rawPollMax !== undefined) {
|
|
81
|
-
const minMs = Number.parseInt(rawPollMin ?? "", 10);
|
|
82
|
-
const maxMs = Number.parseInt(rawPollMax ?? "", 10);
|
|
83
|
-
if (Number.isNaN(minMs) || Number.isNaN(maxMs) || minMs <= 0 || maxMs < minMs) {
|
|
84
|
-
throw new Error("CELLO_MANIFEST_POLL_MIN_MS / _MAX_MS must BOTH be set to positive integers with min <= max");
|
|
85
|
-
}
|
|
86
|
-
pollOpts = { minMs, maxMs };
|
|
87
|
-
}
|
|
88
|
-
const manifestPollScheduler = new RandomizedPollScheduler(pollOpts);
|
|
89
|
-
logger.info("daemon.manifest.configured", {
|
|
90
|
-
manifestPath,
|
|
91
|
-
rootKeyCount: manifestRootKeys.length,
|
|
92
|
-
threshold: manifestThreshold,
|
|
93
|
-
pollMinMs: pollOpts?.minMs ?? null,
|
|
94
|
-
pollMaxMs: pollOpts?.maxMs ?? null,
|
|
95
|
-
});
|
|
96
|
-
return { manifestProvider, manifestRootKeys, manifestThreshold, challengeVerifier, manifestPollScheduler };
|
|
97
|
-
}
|
|
98
41
|
async function main() {
|
|
99
42
|
// M7 Keystone (Part 1): give the daemon its door to the directory. The resolver
|
|
100
43
|
// discovers the directory endpoint via GET ${CELLO_DIRECTORY_URL}/bootstrap (the
|
|
@@ -105,12 +48,16 @@ async function main() {
|
|
|
105
48
|
// null on a fresh /bootstrap failure — WITHOUT it, the wrapper would keep receiving the stale dead
|
|
106
49
|
// endpoint and never fail over (the exact live kill-primary bug). The roster is the real fallback.
|
|
107
50
|
const directoryEndpointResolver = createDirectoryEndpointResolver({ logger, staleFallback: false });
|
|
108
|
-
// M7 J-AUTH (DOD-AUTH-1/2):
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// daemon
|
|
113
|
-
//
|
|
51
|
+
// M7 J-AUTH (DOD-AUTH-1/2) + FINDING-4: consortium-manifest deps. buildManifestDeps chooses:
|
|
52
|
+
// - DEFAULT (no CELLO_CONSORTIUM_MANIFEST) — load the COMPILED-IN production roster + step-6
|
|
53
|
+
// directory identity auth, so a cold-boot daemon knows every directory and can fail over to a
|
|
54
|
+
// reachable one (redundancy invariant). Gated on CELLO_DIRECTORY_URL actually being a bundled
|
|
55
|
+
// node — a daemon pointed at a local/non-bundled directory (local dev, e2e spine harness) gets
|
|
56
|
+
// the M6 backward-compat path (no roster, no step-6) instead of wrongly failing step-6.
|
|
57
|
+
// - OVERRIDE (CELLO_CONSORTIUM_MANIFEST set) — operator-supplied manifest FILE + env root keys /
|
|
58
|
+
// threshold + optional /manifest poll (the pre-FINDING-4 opt-in path).
|
|
59
|
+
// When a manifest is active, the daemon verifies the directory's step-6 identity proof against the
|
|
60
|
+
// node pubkeys in it. Both dialers (keystone + per-agent) receive the verifier via startDaemon.
|
|
114
61
|
const manifest = buildManifestDeps(logger);
|
|
115
62
|
const handle = await startDaemon({
|
|
116
63
|
celloDir,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cello-daemon.js","sourceRoot":"","sources":["../../src/bin/cello-daemon.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,+BAA+B,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cello-daemon.js","sourceRoot":"","sources":["../../src/bin/cello-daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FINDING-4 — the bundled consortium roster (sovereign-node REDUNDANCY invariant).
|
|
3
|
+
*
|
|
4
|
+
* This is the signed list of the sovereign directory nodes that make up the CELLO consortium,
|
|
5
|
+
* COMPILED INTO the client. It is loaded by default (when the operator does not override it via
|
|
6
|
+
* CELLO_CONSORTIUM_MANIFEST) so that a cold-boot daemon already knows every directory — and can
|
|
7
|
+
* fail over to a reachable one — even when its configured/primary directory is unreachable. Without
|
|
8
|
+
* a bundled roster the failover resolver has nothing to route to (the exact FINDING-4 gap: the
|
|
9
|
+
* roster-aware dialer was correct but the roster was empty), so a single directory being down
|
|
10
|
+
* stranded the client at startup, violating the redundancy invariant.
|
|
11
|
+
*
|
|
12
|
+
* Trust model:
|
|
13
|
+
* - The manifest is threshold-signed by the consortium officer key(s). At load the client
|
|
14
|
+
* RE-VERIFIES it against BUNDLED_CONSORTIUM_ROOT_KEYS (a self-consistency / anti-corruption
|
|
15
|
+
* gate — the manifest and root keys ship together, so this catches a bad regeneration, not a
|
|
16
|
+
* network adversary).
|
|
17
|
+
* - The node `pubkey`s are the directories' Ed25519 node keys. They are the trust anchor for
|
|
18
|
+
* step-6 directory identity auth (M7-MANIFEST-002): when the client connects to (or fails over
|
|
19
|
+
* to) a directory, the directory must sign the client's challenge with the matching node key,
|
|
20
|
+
* proving it is the real consortium node for that nodeId — defeating a MITM on the plaintext
|
|
21
|
+
* /bootstrap that would otherwise redirect the client to a rogue directory. THIS is the
|
|
22
|
+
* adversarial defense; it is enabled by default alongside the roster.
|
|
23
|
+
*
|
|
24
|
+
* nodeId MUST equal the directory's reported NODE_ID (its AWS region string); the step-6 verifier
|
|
25
|
+
* looks the node up by nodeId. pubkey MUST equal /cello/<env>/directory/manifest-signer-pubkey for
|
|
26
|
+
* that region (derived from the directory's node-private-key). endpoint is the HTTP /bootstrap base.
|
|
27
|
+
*
|
|
28
|
+
* Regeneration: on directory node-key or officer-key rotation, re-sign with
|
|
29
|
+
* infra/scripts/sign-consortium-manifest and update this constant + BUNDLED_CONSORTIUM_ROOT_KEYS.
|
|
30
|
+
* The officer signing key lives in Secrets Manager (cello/<env>/consortium/officer-key-0); only its
|
|
31
|
+
* PUBLIC key is embedded here.
|
|
32
|
+
*
|
|
33
|
+
* Crypto reference: RFC 8032 (Ed25519 threshold signatures, verified by verifyManifest).
|
|
34
|
+
*/
|
|
35
|
+
import type { ConsortiumManifestInput } from "@cello-protocol/crypto";
|
|
36
|
+
/** The signed consortium manifest (roster of sovereign directory nodes). */
|
|
37
|
+
export declare const BUNDLED_CONSORTIUM_MANIFEST: ConsortiumManifestInput;
|
|
38
|
+
/**
|
|
39
|
+
* Pinned consortium officer root public key(s). Officer 0's Ed25519 public key (hex). The client
|
|
40
|
+
* verifies BUNDLED_CONSORTIUM_MANIFEST's threshold signatures against this set. Public data.
|
|
41
|
+
*/
|
|
42
|
+
export declare const BUNDLED_CONSORTIUM_ROOT_KEYS: readonly string[];
|
|
43
|
+
/** Minimum number of distinct valid officer signatures required (dev: single officer, threshold 1). */
|
|
44
|
+
export declare const BUNDLED_CONSORTIUM_THRESHOLD = 1;
|
|
45
|
+
//# sourceMappingURL=bundled-consortium-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundled-consortium-manifest.d.ts","sourceRoot":"","sources":["../src/bundled-consortium-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,4EAA4E;AAC5E,eAAO,MAAM,2BAA2B,EAAE,uBAkCzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAEzD,CAAC;AAEF,uGAAuG;AACvG,eAAO,MAAM,4BAA4B,IAAI,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FINDING-4 — the bundled consortium roster (sovereign-node REDUNDANCY invariant).
|
|
3
|
+
*
|
|
4
|
+
* This is the signed list of the sovereign directory nodes that make up the CELLO consortium,
|
|
5
|
+
* COMPILED INTO the client. It is loaded by default (when the operator does not override it via
|
|
6
|
+
* CELLO_CONSORTIUM_MANIFEST) so that a cold-boot daemon already knows every directory — and can
|
|
7
|
+
* fail over to a reachable one — even when its configured/primary directory is unreachable. Without
|
|
8
|
+
* a bundled roster the failover resolver has nothing to route to (the exact FINDING-4 gap: the
|
|
9
|
+
* roster-aware dialer was correct but the roster was empty), so a single directory being down
|
|
10
|
+
* stranded the client at startup, violating the redundancy invariant.
|
|
11
|
+
*
|
|
12
|
+
* Trust model:
|
|
13
|
+
* - The manifest is threshold-signed by the consortium officer key(s). At load the client
|
|
14
|
+
* RE-VERIFIES it against BUNDLED_CONSORTIUM_ROOT_KEYS (a self-consistency / anti-corruption
|
|
15
|
+
* gate — the manifest and root keys ship together, so this catches a bad regeneration, not a
|
|
16
|
+
* network adversary).
|
|
17
|
+
* - The node `pubkey`s are the directories' Ed25519 node keys. They are the trust anchor for
|
|
18
|
+
* step-6 directory identity auth (M7-MANIFEST-002): when the client connects to (or fails over
|
|
19
|
+
* to) a directory, the directory must sign the client's challenge with the matching node key,
|
|
20
|
+
* proving it is the real consortium node for that nodeId — defeating a MITM on the plaintext
|
|
21
|
+
* /bootstrap that would otherwise redirect the client to a rogue directory. THIS is the
|
|
22
|
+
* adversarial defense; it is enabled by default alongside the roster.
|
|
23
|
+
*
|
|
24
|
+
* nodeId MUST equal the directory's reported NODE_ID (its AWS region string); the step-6 verifier
|
|
25
|
+
* looks the node up by nodeId. pubkey MUST equal /cello/<env>/directory/manifest-signer-pubkey for
|
|
26
|
+
* that region (derived from the directory's node-private-key). endpoint is the HTTP /bootstrap base.
|
|
27
|
+
*
|
|
28
|
+
* Regeneration: on directory node-key or officer-key rotation, re-sign with
|
|
29
|
+
* infra/scripts/sign-consortium-manifest and update this constant + BUNDLED_CONSORTIUM_ROOT_KEYS.
|
|
30
|
+
* The officer signing key lives in Secrets Manager (cello/<env>/consortium/officer-key-0); only its
|
|
31
|
+
* PUBLIC key is embedded here.
|
|
32
|
+
*
|
|
33
|
+
* Crypto reference: RFC 8032 (Ed25519 threshold signatures, verified by verifyManifest).
|
|
34
|
+
*/
|
|
35
|
+
/** The signed consortium manifest (roster of sovereign directory nodes). */
|
|
36
|
+
export const BUNDLED_CONSORTIUM_MANIFEST = {
|
|
37
|
+
version: 1,
|
|
38
|
+
not_before: "2026-01-01T00:00:00Z",
|
|
39
|
+
expires: "2030-01-01T00:00:00Z",
|
|
40
|
+
nodes: [
|
|
41
|
+
{
|
|
42
|
+
nodeId: "us-east-1",
|
|
43
|
+
pubkey: "167ca6b145bfdd3696af8f4befd883c3dc610f4a9c8d52a30f6a22f669dc27b5",
|
|
44
|
+
region: "us-east-1",
|
|
45
|
+
provider: "aws",
|
|
46
|
+
endpoint: "http://directory-us1.cello.mygentic.ai",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
nodeId: "eu-central-1",
|
|
50
|
+
pubkey: "8105b180b753d97b50039a7e94433fd2b419f43d61f9ad7caf2ac15ad5cd1b45",
|
|
51
|
+
region: "eu-central-1",
|
|
52
|
+
provider: "aws",
|
|
53
|
+
endpoint: "http://directory-eu1.cello.mygentic.ai",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
nodeId: "ap-northeast-1",
|
|
57
|
+
pubkey: "9b4b673a16487ba47363e3eaff844bf68f19736d82967918fb896b813e39b984",
|
|
58
|
+
region: "ap-northeast-1",
|
|
59
|
+
provider: "aws",
|
|
60
|
+
endpoint: "http://directory-ap1.cello.mygentic.ai",
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
signatures: [
|
|
64
|
+
{
|
|
65
|
+
officerIndex: 0,
|
|
66
|
+
signature: "1a372676e83ae323be8dbc6f8b786b3424706999efcccd70027798cbca91fe0b60cb35ac1e1af21078f24618d1f740b61bdcf06da03c5f90acc48d954039a104",
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Pinned consortium officer root public key(s). Officer 0's Ed25519 public key (hex). The client
|
|
72
|
+
* verifies BUNDLED_CONSORTIUM_MANIFEST's threshold signatures against this set. Public data.
|
|
73
|
+
*/
|
|
74
|
+
export const BUNDLED_CONSORTIUM_ROOT_KEYS = [
|
|
75
|
+
"8e9b99e5aa5505f2f50ec9f933f497a64956614575edce3427ec8a096c864199",
|
|
76
|
+
];
|
|
77
|
+
/** Minimum number of distinct valid officer signatures required (dev: single officer, threshold 1). */
|
|
78
|
+
export const BUNDLED_CONSORTIUM_THRESHOLD = 1;
|
|
79
|
+
//# sourceMappingURL=bundled-consortium-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundled-consortium-manifest.js","sourceRoot":"","sources":["../src/bundled-consortium-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,4EAA4E;AAC5E,MAAM,CAAC,MAAM,2BAA2B,GAA4B;IAClE,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,sBAAsB;IAClC,OAAO,EAAE,sBAAsB;IAC/B,KAAK,EAAE;QACL;YACE,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE,kEAAkE;YAC1E,MAAM,EAAE,WAAW;YACnB,QAAQ,EAAE,KAAK;YACf,QAAQ,EAAE,wCAAwC;SACnD;QACD;YACE,MAAM,EAAE,cAAc;YACtB,MAAM,EAAE,kEAAkE;YAC1E,MAAM,EAAE,cAAc;YACtB,QAAQ,EAAE,KAAK;YACf,QAAQ,EAAE,wCAAwC;SACnD;QACD;YACE,MAAM,EAAE,gBAAgB;YACxB,MAAM,EAAE,kEAAkE;YAC1E,MAAM,EAAE,gBAAgB;YACxB,QAAQ,EAAE,KAAK;YACf,QAAQ,EAAE,wCAAwC;SACnD;KACF;IACD,UAAU,EAAE;QACV;YACE,YAAY,EAAE,CAAC;YACf,SAAS,EACP,kIAAkI;SACrI;KACF;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAsB;IAC7D,kEAAkE;CACnE,CAAC;AAEF,uGAAuG;AACvG,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ConsortiumManifest } from "@cello-protocol/protocol-types";
|
|
2
|
+
import { type ConsortiumManifestInput } from "@cello-protocol/crypto";
|
|
2
3
|
import type { IManifestProvider } from "@cello-protocol/transport";
|
|
3
4
|
export declare class ManifestLoadError extends Error {
|
|
4
5
|
readonly reason: string;
|
|
@@ -15,4 +16,22 @@ export declare class FileManifestProvider implements IManifestProvider {
|
|
|
15
16
|
getCurrentManifest(): ConsortiumManifest | null;
|
|
16
17
|
updateManifest(manifest: ConsortiumManifest): void;
|
|
17
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* EmbeddedManifestProvider — the production provider for the COMPILED-IN consortium roster
|
|
21
|
+
* (FINDING-4). Identical trust gate to FileManifestProvider (threshold officer-signature
|
|
22
|
+
* verification via `verifyManifest`, rejects an empty node set) but the manifest is an in-memory
|
|
23
|
+
* constant compiled into the client rather than a file on disk. This is what the daemon wires by
|
|
24
|
+
* default (no CELLO_CONSORTIUM_MANIFEST override), so the roster and step-6 directory-auth anchor
|
|
25
|
+
* are always present — they cannot be lost by a missing package file or a skipped build-copy step,
|
|
26
|
+
* and a corrupt/mis-signed embedded manifest fails CLOSED at load (ADV-002) rather than silently
|
|
27
|
+
* degrading. Expiry / version monotonicity remain the daemon's policy layer, exactly as for
|
|
28
|
+
* FileManifestProvider (see that class's note) — this provider does structure + signatures only.
|
|
29
|
+
*/
|
|
30
|
+
export declare class EmbeddedManifestProvider implements IManifestProvider {
|
|
31
|
+
#private;
|
|
32
|
+
constructor(input: ConsortiumManifestInput);
|
|
33
|
+
loadAndVerify(rootKeys: readonly string[], threshold: number): Promise<ConsortiumManifest>;
|
|
34
|
+
getCurrentManifest(): ConsortiumManifest | null;
|
|
35
|
+
updateManifest(manifest: ConsortiumManifest): void;
|
|
36
|
+
}
|
|
18
37
|
//# sourceMappingURL=file-manifest-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-manifest-provider.d.ts","sourceRoot":"","sources":["../src/file-manifest-provider.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"file-manifest-provider.d.ts","sourceRoot":"","sources":["../src/file-manifest-provider.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAkB,KAAK,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnE,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAK5C;AAED,MAAM,WAAW,wBAAwB;IACvC,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,oBAAqB,YAAW,iBAAiB;;gBAIhD,IAAI,EAAE,wBAAwB;IAIpC,aAAa,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA2BhG,kBAAkB,IAAI,kBAAkB,GAAG,IAAI;IAI/C,cAAc,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI;CAGnD;AAED;;;;;;;;;;GAUG;AACH,qBAAa,wBAAyB,YAAW,iBAAiB;;gBAIpD,KAAK,EAAE,uBAAuB;IAI1C,aAAa,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAS1F,kBAAkB,IAAI,kBAAkB,GAAG,IAAI;IAI/C,cAAc,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI;CAGnD"}
|
|
@@ -69,4 +69,36 @@ export class FileManifestProvider {
|
|
|
69
69
|
this.#manifest = manifest;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* EmbeddedManifestProvider — the production provider for the COMPILED-IN consortium roster
|
|
74
|
+
* (FINDING-4). Identical trust gate to FileManifestProvider (threshold officer-signature
|
|
75
|
+
* verification via `verifyManifest`, rejects an empty node set) but the manifest is an in-memory
|
|
76
|
+
* constant compiled into the client rather than a file on disk. This is what the daemon wires by
|
|
77
|
+
* default (no CELLO_CONSORTIUM_MANIFEST override), so the roster and step-6 directory-auth anchor
|
|
78
|
+
* are always present — they cannot be lost by a missing package file or a skipped build-copy step,
|
|
79
|
+
* and a corrupt/mis-signed embedded manifest fails CLOSED at load (ADV-002) rather than silently
|
|
80
|
+
* degrading. Expiry / version monotonicity remain the daemon's policy layer, exactly as for
|
|
81
|
+
* FileManifestProvider (see that class's note) — this provider does structure + signatures only.
|
|
82
|
+
*/
|
|
83
|
+
export class EmbeddedManifestProvider {
|
|
84
|
+
#input;
|
|
85
|
+
#manifest = null;
|
|
86
|
+
constructor(input) {
|
|
87
|
+
this.#input = input;
|
|
88
|
+
}
|
|
89
|
+
loadAndVerify(rootKeys, threshold) {
|
|
90
|
+
const result = verifyManifest(this.#input, rootKeys, threshold);
|
|
91
|
+
if (!result.ok) {
|
|
92
|
+
return Promise.reject(new ManifestLoadError("manifest_signature_invalid", result.detail));
|
|
93
|
+
}
|
|
94
|
+
this.#manifest = this.#input;
|
|
95
|
+
return Promise.resolve(this.#manifest);
|
|
96
|
+
}
|
|
97
|
+
getCurrentManifest() {
|
|
98
|
+
return this.#manifest;
|
|
99
|
+
}
|
|
100
|
+
updateManifest(manifest) {
|
|
101
|
+
this.#manifest = manifest;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
72
104
|
//# sourceMappingURL=file-manifest-provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-manifest-provider.js","sourceRoot":"","sources":["../src/file-manifest-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,cAAc,EAAgC,MAAM,wBAAwB,CAAC;AAGtF,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,MAAM,CAAS;IACxB,YAAY,MAAc,EAAE,MAAe;QACzC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAOD,MAAM,OAAO,oBAAoB;IACtB,KAAK,CAAS;IACvB,SAAS,GAA8B,IAAI,CAAC;IAE5C,YAAY,IAA8B;QACxC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,QAA2B,EAAE,SAAiB;QAChE,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,IAAI,iBAAiB,CAAC,qBAAqB,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACvG,CAAC;QAED,IAAI,MAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,CAAC;QACjD,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,IAAI,iBAAiB,CAAC,oBAAoB,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACtG,CAAC;QAED,2EAA2E;QAC3E,kFAAkF;QAClF,oEAAoE;QACpE,MAAM,MAAM,GAAG,cAAc,CAAC,MAA4C,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QACjG,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,IAAI,iBAAiB,CAAC,4BAA4B,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3E,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;QACxB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,kBAAkB;QAChB,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,cAAc,CAAC,QAA4B;QACzC,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC5B,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"file-manifest-provider.js","sourceRoot":"","sources":["../src/file-manifest-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,cAAc,EAAgC,MAAM,wBAAwB,CAAC;AAGtF,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,MAAM,CAAS;IACxB,YAAY,MAAc,EAAE,MAAe;QACzC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAOD,MAAM,OAAO,oBAAoB;IACtB,KAAK,CAAS;IACvB,SAAS,GAA8B,IAAI,CAAC;IAE5C,YAAY,IAA8B;QACxC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,QAA2B,EAAE,SAAiB;QAChE,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,IAAI,iBAAiB,CAAC,qBAAqB,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACvG,CAAC;QAED,IAAI,MAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,CAAC;QACjD,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,IAAI,iBAAiB,CAAC,oBAAoB,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACtG,CAAC;QAED,2EAA2E;QAC3E,kFAAkF;QAClF,oEAAoE;QACpE,MAAM,MAAM,GAAG,cAAc,CAAC,MAA4C,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QACjG,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,IAAI,iBAAiB,CAAC,4BAA4B,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3E,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;QACxB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,kBAAkB;QAChB,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,cAAc,CAAC,QAA4B;QACzC,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC5B,CAAC;CACF;AAED;;;;;;;;;;GAUG;AACH,MAAM,OAAO,wBAAwB;IAC1B,MAAM,CAA0B;IACzC,SAAS,GAA8B,IAAI,CAAC;IAE5C,YAAY,KAA8B;QACxC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,aAAa,CAAC,QAA2B,EAAE,SAAiB;QAC1D,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QAChE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,iBAAiB,CAAC,4BAA4B,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5F,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAuC,CAAC;QAC9D,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC;IAED,kBAAkB;QAChB,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,cAAc,CAAC,QAA4B;QACzC,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composition helper for the daemon's consortium-manifest dependencies (M7 J-AUTH / FINDING-4).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from bin/cello-daemon.ts so it can be unit-tested without triggering the bin's
|
|
5
|
+
* top-level main() (which spawns a live daemon on import).
|
|
6
|
+
*
|
|
7
|
+
* Two modes:
|
|
8
|
+
* - DEFAULT (no CELLO_CONSORTIUM_MANIFEST): load the COMPILED-IN consortium roster
|
|
9
|
+
* (bundled-consortium-manifest.ts) with step-6 directory identity auth ON — the full production
|
|
10
|
+
* posture that makes cold-boot directory failover possible (FINDING-4 redundancy invariant).
|
|
11
|
+
* - OVERRIDE (CELLO_CONSORTIUM_MANIFEST set): operator-supplied manifest FILE + env root keys /
|
|
12
|
+
* threshold, plus the optional background /manifest poll. The pre-FINDING-4 opt-in path.
|
|
13
|
+
*/
|
|
14
|
+
import { type IManifestProvider, type IDirectoryChallengeVerifier, type IManifestVersionStore, type IManifestPollScheduler } from "@cello-protocol/transport";
|
|
15
|
+
import type { Logger } from "./types.js";
|
|
16
|
+
export interface ManifestDeps {
|
|
17
|
+
manifestProvider?: IManifestProvider;
|
|
18
|
+
manifestRootKeys?: readonly string[];
|
|
19
|
+
manifestThreshold?: number;
|
|
20
|
+
challengeVerifier?: IDirectoryChallengeVerifier;
|
|
21
|
+
manifestVersionStore?: IManifestVersionStore;
|
|
22
|
+
manifestPollScheduler?: IManifestPollScheduler;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build the consortium-manifest deps.
|
|
26
|
+
*
|
|
27
|
+
* CELLO_CONSORTIUM_MANIFEST absolute path to an override manifest JSON (opt-in)
|
|
28
|
+
* CELLO_CONSORTIUM_ROOT_KEYS comma-separated officer root pubkeys (hex) — override path only
|
|
29
|
+
* CELLO_CONSORTIUM_THRESHOLD minimum officer signatures (integer) — override path only
|
|
30
|
+
* CELLO_MANIFEST_POLL_MIN_MS / _MAX_MS optional background poll window — override path only
|
|
31
|
+
*
|
|
32
|
+
* The single manifestProvider instance is shared between startDaemon's loadAndVerify call and the
|
|
33
|
+
* ManifestDirectoryChallengeVerifier, so step-6 reads the same cached, verified manifest.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildManifestDeps(logger: Logger): ManifestDeps;
|
|
36
|
+
//# sourceMappingURL=manifest-deps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-deps.d.ts","sourceRoot":"","sources":["../src/manifest-deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,2BAA2B,CAAC;AASnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAYzC,MAAM,WAAW,YAAY;IAC3B,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,2BAA2B,CAAC;IAChD,oBAAoB,CAAC,EAAE,qBAAqB,CAAC;IAC7C,qBAAqB,CAAC,EAAE,sBAAsB,CAAC;CAChD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAgF9D"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composition helper for the daemon's consortium-manifest dependencies (M7 J-AUTH / FINDING-4).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from bin/cello-daemon.ts so it can be unit-tested without triggering the bin's
|
|
5
|
+
* top-level main() (which spawns a live daemon on import).
|
|
6
|
+
*
|
|
7
|
+
* Two modes:
|
|
8
|
+
* - DEFAULT (no CELLO_CONSORTIUM_MANIFEST): load the COMPILED-IN consortium roster
|
|
9
|
+
* (bundled-consortium-manifest.ts) with step-6 directory identity auth ON — the full production
|
|
10
|
+
* posture that makes cold-boot directory failover possible (FINDING-4 redundancy invariant).
|
|
11
|
+
* - OVERRIDE (CELLO_CONSORTIUM_MANIFEST set): operator-supplied manifest FILE + env root keys /
|
|
12
|
+
* threshold, plus the optional background /manifest poll. The pre-FINDING-4 opt-in path.
|
|
13
|
+
*/
|
|
14
|
+
import { ManifestDirectoryChallengeVerifier, } from "@cello-protocol/transport";
|
|
15
|
+
import { FileManifestProvider, EmbeddedManifestProvider } from "./file-manifest-provider.js";
|
|
16
|
+
import { BUNDLED_CONSORTIUM_MANIFEST, BUNDLED_CONSORTIUM_ROOT_KEYS, BUNDLED_CONSORTIUM_THRESHOLD, } from "./bundled-consortium-manifest.js";
|
|
17
|
+
import { resolveDirectoryUrl } from "./directory-bootstrap.js";
|
|
18
|
+
import { RandomizedPollScheduler } from "./manifest-poll-scheduler.js";
|
|
19
|
+
/** Normalize a directory URL for comparison (trim, drop trailing slashes, lowercase). */
|
|
20
|
+
function normalizeUrl(u) {
|
|
21
|
+
return u.trim().replace(/\/+$/, "").toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
/** The set of directory endpoints the bundled roster describes (normalized). */
|
|
24
|
+
const BUNDLED_ENDPOINTS = new Set(BUNDLED_CONSORTIUM_MANIFEST.nodes.map((n) => normalizeUrl(String(n["endpoint"]))));
|
|
25
|
+
/**
|
|
26
|
+
* Build the consortium-manifest deps.
|
|
27
|
+
*
|
|
28
|
+
* CELLO_CONSORTIUM_MANIFEST absolute path to an override manifest JSON (opt-in)
|
|
29
|
+
* CELLO_CONSORTIUM_ROOT_KEYS comma-separated officer root pubkeys (hex) — override path only
|
|
30
|
+
* CELLO_CONSORTIUM_THRESHOLD minimum officer signatures (integer) — override path only
|
|
31
|
+
* CELLO_MANIFEST_POLL_MIN_MS / _MAX_MS optional background poll window — override path only
|
|
32
|
+
*
|
|
33
|
+
* The single manifestProvider instance is shared between startDaemon's loadAndVerify call and the
|
|
34
|
+
* ManifestDirectoryChallengeVerifier, so step-6 reads the same cached, verified manifest.
|
|
35
|
+
*/
|
|
36
|
+
export function buildManifestDeps(logger) {
|
|
37
|
+
const manifestPath = process.env.CELLO_CONSORTIUM_MANIFEST;
|
|
38
|
+
if (!manifestPath) {
|
|
39
|
+
// FINDING-4 default: load the COMPILED-IN consortium roster so the daemon already knows every
|
|
40
|
+
// sovereign directory — and can fail over to a reachable one — even when its primary is down
|
|
41
|
+
// (the redundancy invariant), AND enforces step-6 directory identity auth against the bundled
|
|
42
|
+
// node pubkeys (defeats a /bootstrap MITM redirecting failover to a rogue directory). This is
|
|
43
|
+
// the full production posture with zero operator config. The manifest is re-verified against the
|
|
44
|
+
// pinned root keys at load by startDaemon (ADV-002 — a corrupt embedded manifest fails CLOSED,
|
|
45
|
+
// never a silent downgrade). No poll scheduler: the bundled roster is static; adopting runtime
|
|
46
|
+
// manifest updates via the /manifest poll is a separate opt-in reachable through the env path.
|
|
47
|
+
//
|
|
48
|
+
// GATE (do NOT enable step-6 against a directory the bundle can't authenticate): the bundled
|
|
49
|
+
// manifest is the trust anchor for the PRODUCTION consortium ONLY (the mygentic.ai directories
|
|
50
|
+
// it lists). When the operator has pointed CELLO_DIRECTORY_URL at a directory that is NOT in the
|
|
51
|
+
// bundle — local dev (`CELLO_ENV=local`) and the e2e spine harness both run their own local
|
|
52
|
+
// directory on 127.0.0.1 — the bundled node pubkeys don't describe it, so enforcing step-6 would
|
|
53
|
+
// wrongly reject every connection (key_not_in_manifest). In that case fall through to the M6
|
|
54
|
+
// backward-compat path (no roster, no step-6); those deployments opt into step-6 explicitly by
|
|
55
|
+
// supplying a matching manifest via CELLO_CONSORTIUM_MANIFEST. Unset URL → the production default
|
|
56
|
+
// (directory-us1.cello.mygentic.ai), which IS in the bundle, so real operators get the full posture.
|
|
57
|
+
const directoryUrl = normalizeUrl(resolveDirectoryUrl());
|
|
58
|
+
if (!BUNDLED_ENDPOINTS.has(directoryUrl)) {
|
|
59
|
+
logger.info("daemon.manifest.bundled.skipped", { directoryUrl, reason: "directory_not_in_bundled_roster" });
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
const manifestProvider = new EmbeddedManifestProvider(BUNDLED_CONSORTIUM_MANIFEST);
|
|
63
|
+
const challengeVerifier = new ManifestDirectoryChallengeVerifier(manifestProvider);
|
|
64
|
+
logger.info("daemon.manifest.bundled", {
|
|
65
|
+
version: BUNDLED_CONSORTIUM_MANIFEST.version,
|
|
66
|
+
nodeCount: BUNDLED_CONSORTIUM_MANIFEST.nodes.length,
|
|
67
|
+
rootKeyCount: BUNDLED_CONSORTIUM_ROOT_KEYS.length,
|
|
68
|
+
threshold: BUNDLED_CONSORTIUM_THRESHOLD,
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
manifestProvider,
|
|
72
|
+
manifestRootKeys: BUNDLED_CONSORTIUM_ROOT_KEYS,
|
|
73
|
+
manifestThreshold: BUNDLED_CONSORTIUM_THRESHOLD,
|
|
74
|
+
challengeVerifier,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const rootKeysRaw = process.env.CELLO_CONSORTIUM_ROOT_KEYS ?? "";
|
|
78
|
+
const manifestRootKeys = rootKeysRaw.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
|
|
79
|
+
const manifestThreshold = Number.parseInt(process.env.CELLO_CONSORTIUM_THRESHOLD ?? "", 10);
|
|
80
|
+
if (manifestRootKeys.length === 0 || Number.isNaN(manifestThreshold)) {
|
|
81
|
+
throw new Error("CELLO_CONSORTIUM_MANIFEST is set but CELLO_CONSORTIUM_ROOT_KEYS / CELLO_CONSORTIUM_THRESHOLD are missing or invalid");
|
|
82
|
+
}
|
|
83
|
+
const manifestProvider = new FileManifestProvider({ path: manifestPath });
|
|
84
|
+
const challengeVerifier = new ManifestDirectoryChallengeVerifier(manifestProvider);
|
|
85
|
+
// DOD-AUTH-2: background manifest poll. The directory is re-polled on a randomized 6–12h interval
|
|
86
|
+
// (thundering-herd avoidance) and a newer signed manifest is adopted. The interval is env-injectable
|
|
87
|
+
// so the live binary test can poll sub-second instead of waiting hours; production leaves these
|
|
88
|
+
// unset → the 6–12h default window. Both-or-neither, positive, min <= max — a partial/invalid
|
|
89
|
+
// override fails LOUDLY rather than silently reverting or producing a negative (tight-loop) delay.
|
|
90
|
+
const rawPollMin = process.env.CELLO_MANIFEST_POLL_MIN_MS;
|
|
91
|
+
const rawPollMax = process.env.CELLO_MANIFEST_POLL_MAX_MS;
|
|
92
|
+
let pollOpts;
|
|
93
|
+
if (rawPollMin !== undefined || rawPollMax !== undefined) {
|
|
94
|
+
const minMs = Number.parseInt(rawPollMin ?? "", 10);
|
|
95
|
+
const maxMs = Number.parseInt(rawPollMax ?? "", 10);
|
|
96
|
+
if (Number.isNaN(minMs) || Number.isNaN(maxMs) || minMs <= 0 || maxMs < minMs) {
|
|
97
|
+
throw new Error("CELLO_MANIFEST_POLL_MIN_MS / _MAX_MS must BOTH be set to positive integers with min <= max");
|
|
98
|
+
}
|
|
99
|
+
pollOpts = { minMs, maxMs };
|
|
100
|
+
}
|
|
101
|
+
const manifestPollScheduler = new RandomizedPollScheduler(pollOpts);
|
|
102
|
+
logger.info("daemon.manifest.configured", {
|
|
103
|
+
manifestPath,
|
|
104
|
+
rootKeyCount: manifestRootKeys.length,
|
|
105
|
+
threshold: manifestThreshold,
|
|
106
|
+
pollMinMs: pollOpts?.minMs ?? null,
|
|
107
|
+
pollMaxMs: pollOpts?.maxMs ?? null,
|
|
108
|
+
});
|
|
109
|
+
return { manifestProvider, manifestRootKeys, manifestThreshold, challengeVerifier, manifestPollScheduler };
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=manifest-deps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-deps.js","sourceRoot":"","sources":["../src/manifest-deps.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cello-protocol/daemon",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"@signalapp/sqlcipher": "^3.3.5",
|
|
31
31
|
"cbor-x": "^1.6.0",
|
|
32
32
|
"it-length-prefixed": "^10.0.1",
|
|
33
|
-
"@cello-protocol/protocol-types": "0.0.11",
|
|
34
33
|
"@cello-protocol/crypto": "0.0.14",
|
|
35
|
-
"@cello-protocol/transport": "0.0.11"
|
|
34
|
+
"@cello-protocol/transport": "0.0.11",
|
|
35
|
+
"@cello-protocol/protocol-types": "0.0.11"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^25.6.2",
|