@cello-protocol/daemon 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/agent-loader.d.ts +15 -18
  2. package/dist/agent-loader.d.ts.map +1 -1
  3. package/dist/agent-loader.js +24 -81
  4. package/dist/agent-loader.js.map +1 -1
  5. package/dist/bin/cello-daemon.js +5 -7
  6. package/dist/bin/cello-daemon.js.map +1 -1
  7. package/dist/daemon.d.ts.map +1 -1
  8. package/dist/daemon.js +133 -66
  9. package/dist/daemon.js.map +1 -1
  10. package/dist/db-identity-store.d.ts +97 -0
  11. package/dist/db-identity-store.d.ts.map +1 -0
  12. package/dist/db-identity-store.js +235 -0
  13. package/dist/db-identity-store.js.map +1 -0
  14. package/dist/identity-migration.d.ts +40 -0
  15. package/dist/identity-migration.d.ts.map +1 -0
  16. package/dist/identity-migration.js +455 -0
  17. package/dist/identity-migration.js.map +1 -0
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/manifest-version-store-db.d.ts +24 -0
  23. package/dist/manifest-version-store-db.d.ts.map +1 -0
  24. package/dist/manifest-version-store-db.js +57 -0
  25. package/dist/manifest-version-store-db.js.map +1 -0
  26. package/dist/manifest-version-store.d.ts +3 -1
  27. package/dist/manifest-version-store.d.ts.map +1 -1
  28. package/dist/manifest-version-store.js +3 -1
  29. package/dist/manifest-version-store.js.map +1 -1
  30. package/dist/nonce-dedup.d.ts +2 -2
  31. package/dist/nonce-dedup.d.ts.map +1 -1
  32. package/dist/nonce-dedup.js.map +1 -1
  33. package/dist/registration-manager.d.ts.map +1 -1
  34. package/dist/registration-manager.js +78 -44
  35. package/dist/registration-manager.js.map +1 -1
  36. package/dist/retry-queue.d.ts +2 -3
  37. package/dist/retry-queue.d.ts.map +1 -1
  38. package/dist/retry-queue.js +8 -16
  39. package/dist/retry-queue.js.map +1 -1
  40. package/dist/seal-upgrade.d.ts +3 -1
  41. package/dist/seal-upgrade.d.ts.map +1 -1
  42. package/dist/seal-upgrade.js +1 -2
  43. package/dist/seal-upgrade.js.map +1 -1
  44. package/dist/session-ceremony.d.ts +8 -4
  45. package/dist/session-ceremony.d.ts.map +1 -1
  46. package/dist/session-ceremony.js +3 -7
  47. package/dist/session-ceremony.js.map +1 -1
  48. package/dist/session-node-manager.d.ts +19 -10
  49. package/dist/session-node-manager.d.ts.map +1 -1
  50. package/dist/session-node-manager.js +60 -45
  51. package/dist/session-node-manager.js.map +1 -1
  52. package/dist/sqlcipher-db.d.ts +87 -0
  53. package/dist/sqlcipher-db.d.ts.map +1 -0
  54. package/dist/sqlcipher-db.js +259 -0
  55. package/dist/sqlcipher-db.js.map +1 -0
  56. package/dist/types.d.ts +17 -0
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/types.js.map +1 -1
  59. package/package.json +5 -4
  60. package/dist/manifest-version-store-file.d.ts +0 -18
  61. package/dist/manifest-version-store-file.d.ts.map +0 -1
  62. package/dist/manifest-version-store-file.js +0 -40
  63. package/dist/manifest-version-store-file.js.map +0 -1
  64. package/dist/transcript-cipher.d.ts +0 -31
  65. package/dist/transcript-cipher.d.ts.map +0 -1
  66. package/dist/transcript-cipher.js +0 -74
  67. package/dist/transcript-cipher.js.map +0 -1
@@ -1,31 +1,24 @@
1
1
  /**
2
2
  * Agent identity loader for the CELLO daemon.
3
3
  *
4
- * Pseudocode:
5
- * 1. loadAgents(celloDir, logger):
6
- * a. Check if ~/.cello/agents/ exists
7
- * b. If not, check if ~/.cello/key exists (legacy single-agent mode)
8
- * - If legacy key exists, load it as agent 'default'
9
- * c. If agents/ exists, enumerate subdirectories
10
- * - For each subdir, check if a 'key' file exists
11
- * - If yes, attempt to load it via FileKeyProvider.load()
12
- * - If load fails (corrupt), log agent.load.failed and skip
13
- * - Subdirectories without a 'key' file are silently skipped
14
- * d. Return array of {name, pubkey} for successfully loaded agents
4
+ * CELLO-M7-PERSIST-002 (AC-007): agents are enumerated from the encrypted `agents` table — ONE
5
+ * loading path. The legacy flat-file paths (`~/.cello/agents/<name>/key` and the single-file
6
+ * `~/.cello/key` "default" fallback) are gone; any pre-story key files are imported into the
7
+ * `agents` table by the one-time migration (identity-migration.ts) before this loader runs.
15
8
  *
16
- * Key file format: CELLO binary format (magic bytes + version + 32-byte Ed25519 seed)
17
- * See core/crypto/src/ed25519.ts FileKeyProvider.load() for the canonical parser.
9
+ * Each agent's K_local Ed25519 seed is stored as a BLOB column; the loader builds a sign-only
10
+ * InMemoryKeyProvider from it (the private scalar never leaves the provider — only signatures and
11
+ * content-seal opens are emitted).
18
12
  */
19
13
  import type { KeyProvider } from "@cello-protocol/crypto";
14
+ import type { DaemonDatabase } from "./sqlcipher-db.js";
20
15
  import type { Logger } from "./types.js";
21
16
  export interface LoadedAgent {
22
17
  name: string;
23
18
  pubkey: string;
24
19
  /**
25
- * The agent's K_local signing key, retained so the daemon can produce
26
- * K_local-signed control leaves (e.g. the SEAL-INTERRUPTED leaf in the
27
- * seal-interrupted bilateral flow). The private scalar never leaves this
28
- * provider — only signatures are emitted.
20
+ * The agent's K_local signing key, retained so the daemon can produce K_local-signed control
21
+ * leaves (e.g. the SEAL-INTERRUPTED leaf). The private scalar never leaves this provider.
29
22
  */
30
23
  keyProvider: KeyProvider;
31
24
  }
@@ -37,5 +30,9 @@ export interface AgentLoadResult {
37
30
  loaded: LoadedAgent[];
38
31
  failed: FailedAgent[];
39
32
  }
40
- export declare function loadAgents(celloDir: string, logger: Logger): Promise<AgentLoadResult>;
33
+ /**
34
+ * Load every agent from the encrypted `agents` table. A row whose seed is unusable (corrupt/wrong
35
+ * length) is reported as a failed agent and skipped — never silently dropped.
36
+ */
37
+ export declare function loadAgents(db: DaemonDatabase, logger: Logger): Promise<AgentLoadResult>;
41
38
  //# sourceMappingURL=agent-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-loader.d.ts","sourceRoot":"","sources":["../src/agent-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAwC3F"}
1
+ {"version":3,"file":"agent-loader.d.ts","sourceRoot":"","sources":["../src/agent-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAkB7F"}
@@ -1,94 +1,37 @@
1
1
  /**
2
2
  * Agent identity loader for the CELLO daemon.
3
3
  *
4
- * Pseudocode:
5
- * 1. loadAgents(celloDir, logger):
6
- * a. Check if ~/.cello/agents/ exists
7
- * b. If not, check if ~/.cello/key exists (legacy single-agent mode)
8
- * - If legacy key exists, load it as agent 'default'
9
- * c. If agents/ exists, enumerate subdirectories
10
- * - For each subdir, check if a 'key' file exists
11
- * - If yes, attempt to load it via FileKeyProvider.load()
12
- * - If load fails (corrupt), log agent.load.failed and skip
13
- * - Subdirectories without a 'key' file are silently skipped
14
- * d. Return array of {name, pubkey} for successfully loaded agents
4
+ * CELLO-M7-PERSIST-002 (AC-007): agents are enumerated from the encrypted `agents` table — ONE
5
+ * loading path. The legacy flat-file paths (`~/.cello/agents/<name>/key` and the single-file
6
+ * `~/.cello/key` "default" fallback) are gone; any pre-story key files are imported into the
7
+ * `agents` table by the one-time migration (identity-migration.ts) before this loader runs.
15
8
  *
16
- * Key file format: CELLO binary format (magic bytes + version + 32-byte Ed25519 seed)
17
- * See core/crypto/src/ed25519.ts FileKeyProvider.load() for the canonical parser.
9
+ * Each agent's K_local Ed25519 seed is stored as a BLOB column; the loader builds a sign-only
10
+ * InMemoryKeyProvider from it (the private scalar never leaves the provider — only signatures and
11
+ * content-seal opens are emitted).
18
12
  */
19
- import { readdir, stat, access } from "node:fs/promises";
20
- import { join } from "node:path";
21
- import { FileKeyProvider } from "@cello-protocol/crypto";
22
- export async function loadAgents(celloDir, logger) {
23
- const agentsDir = join(celloDir, "agents");
13
+ import { InMemoryKeyProvider } from "@cello-protocol/crypto";
14
+ import { DbIdentityStore } from "./db-identity-store.js";
15
+ /**
16
+ * Load every agent from the encrypted `agents` table. A row whose seed is unusable (corrupt/wrong
17
+ * length) is reported as a failed agent and skipped — never silently dropped.
18
+ */
19
+ export async function loadAgents(db, logger) {
20
+ const store = new DbIdentityStore(db, logger);
24
21
  const loaded = [];
25
22
  const failed = [];
26
- const agentsDirExists = await directoryExists(agentsDir);
27
- if (!agentsDirExists) {
28
- // Legacy backwards compat: if ~/.cello/key exists and ~/.cello/agents/ doesn't
29
- const legacyKeyPath = join(celloDir, "key");
30
- if (await fileExists(legacyKeyPath)) {
31
- const result = await loadSingleAgent("default", legacyKeyPath, logger);
32
- if (result.ok) {
33
- loaded.push(result.agent);
34
- }
35
- else {
36
- failed.push({ name: "default", error: result.error });
37
- }
38
- }
39
- return { loaded, failed };
40
- }
41
- // Enumerate subdirectories of agents/
42
- const entries = await readdir(agentsDir, { withFileTypes: true });
43
- for (const entry of entries) {
44
- if (!entry.isDirectory())
45
- continue;
46
- const keyPath = join(agentsDir, entry.name, "key");
47
- if (!(await fileExists(keyPath))) {
48
- continue;
23
+ for (const row of store.listAgents()) {
24
+ try {
25
+ const keyProvider = new InMemoryKeyProvider(row.kLocalSeed);
26
+ const pubkey = Buffer.from(await keyProvider.getPublicKey()).toString("hex");
27
+ loaded.push({ name: row.agentName, pubkey, keyProvider });
49
28
  }
50
- const result = await loadSingleAgent(entry.name, keyPath, logger);
51
- if (result.ok) {
52
- loaded.push(result.agent);
53
- }
54
- else {
55
- failed.push({ name: entry.name, error: result.error });
29
+ catch (err) {
30
+ const error = err instanceof Error ? err.message : String(err);
31
+ logger.error("agent.load.failed", { agentName: row.agentName, error });
32
+ failed.push({ name: row.agentName, error });
56
33
  }
57
34
  }
58
35
  return { loaded, failed };
59
36
  }
60
- async function loadSingleAgent(name, keyPath, logger) {
61
- try {
62
- const keyProvider = await FileKeyProvider.load(keyPath);
63
- const pubkeyBytes = await keyProvider.getPublicKey();
64
- const pubkey = Buffer.from(pubkeyBytes).toString("hex");
65
- return { ok: true, agent: { name, pubkey, keyProvider } };
66
- }
67
- catch (err) {
68
- const errorMsg = err instanceof Error ? err.message : String(err);
69
- logger.error("agent.load.failed", {
70
- agentName: name,
71
- error: errorMsg,
72
- });
73
- return { ok: false, error: errorMsg };
74
- }
75
- }
76
- async function directoryExists(path) {
77
- try {
78
- const s = await stat(path);
79
- return s.isDirectory();
80
- }
81
- catch {
82
- return false;
83
- }
84
- }
85
- async function fileExists(path) {
86
- try {
87
- await access(path);
88
- return true;
89
- }
90
- catch {
91
- return false;
92
- }
93
- }
94
37
  //# sourceMappingURL=agent-loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-loader.js","sourceRoot":"","sources":["../src/agent-loader.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-loader.js","sourceRoot":"","sources":["../src/agent-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAwBzD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,EAAkB,EAAE,MAAc;IACjE,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,UAAU,EAAE,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,IAAI,mBAAmB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,WAAW,CAAC,YAAY,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC7E,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;QAC5D,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC/D,MAAM,CAAC,KAAK,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YACvE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC"}
@@ -14,7 +14,6 @@ import { join } from "node:path";
14
14
  import { startDaemon } from "../daemon.js";
15
15
  import { createDirectoryEndpointResolver } from "../directory-bootstrap.js";
16
16
  import { FileManifestProvider } from "../file-manifest-provider.js";
17
- import { FileManifestVersionStore } from "../manifest-version-store-file.js";
18
17
  import { RandomizedPollScheduler } from "../manifest-poll-scheduler.js";
19
18
  import { ManifestDirectoryChallengeVerifier } from "@cello-protocol/transport";
20
19
  const MAX_CONNECTIONS = 16;
@@ -65,11 +64,10 @@ function buildManifestDeps(logger) {
65
64
  }
66
65
  const manifestProvider = new FileManifestProvider({ path: manifestPath });
67
66
  const challengeVerifier = new ManifestDirectoryChallengeVerifier(manifestProvider);
68
- // Anti-rollback (DOD-AUTH-2 / TUF): persist the last-verified manifest version under
69
- // ~/.cello so the daemon refuses a manifest whose version regressed across restarts.
70
- // The daemon (startDaemon) reads getLastSeenVersion() before accepting a manifest and
71
- // persistVersion() on success; a version < trusted directory.auth.manifest.version.rollback.
72
- const manifestVersionStore = new FileManifestVersionStore(join(celloDir, "manifest-version.json"));
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.
73
71
  // DOD-AUTH-2: background manifest poll. The directory is re-polled on a randomized
74
72
  // 6–12h interval (thundering-herd avoidance) and a newer signed manifest is adopted.
75
73
  // The interval is env-injectable so the live binary test can poll sub-second instead
@@ -95,7 +93,7 @@ function buildManifestDeps(logger) {
95
93
  pollMinMs: pollOpts?.minMs ?? null,
96
94
  pollMaxMs: pollOpts?.maxMs ?? null,
97
95
  });
98
- return { manifestProvider, manifestRootKeys, manifestThreshold, challengeVerifier, manifestVersionStore, manifestPollScheduler };
96
+ return { manifestProvider, manifestRootKeys, manifestThreshold, challengeVerifier, manifestPollScheduler };
99
97
  }
100
98
  async function main() {
101
99
  // M7 Keystone (Part 1): give the daemon its door to the directory. The resolver
@@ -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,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAExE,OAAO,EAAE,kCAAkC,EAAqH,MAAM,2BAA2B,CAAC;AAElM,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,uCAAuC;AACvC,MAAM,MAAM,GAAW;IACrB,KAAK,CAAC,KAAa,EAAE,OAAgC;QACnD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACjG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,CAAC,KAAa,EAAE,OAAgC;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAChG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,CAAC,KAAa,EAAE,OAAgC;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAChG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACpC,CAAC;IACD,KAAK,CAAC,KAAa,EAAE,OAAgC;QACnD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACjG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACpC,CAAC;CACF,CAAC;AAEF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AACpE,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;AACjD,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;AACnD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,SAAS,iBAAiB,CAAC,MAAc;IAQvC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;IAC3D,IAAI,CAAC,YAAY;QAAE,OAAO,EAAE,CAAC;IAE7B,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,EAAE,CAAC;IACjE,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjG,MAAM,iBAAiB,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC5F,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,KAAK,CACb,qHAAqH,CACtH,CAAC;IACJ,CAAC;IAED,MAAM,gBAAgB,GAAG,IAAI,oBAAoB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;IAC1E,MAAM,iBAAiB,GAAG,IAAI,kCAAkC,CAAC,gBAAgB,CAAC,CAAC;IACnF,qFAAqF;IACrF,qFAAqF;IACrF,sFAAsF;IACtF,+FAA+F;IAC/F,MAAM,oBAAoB,GAAG,IAAI,wBAAwB,CAAC,IAAI,CAAC,QAAQ,EAAE,uBAAuB,CAAC,CAAC,CAAC;IACnG,mFAAmF;IACnF,qFAAqF;IACrF,qFAAqF;IACrF,8EAA8E;IAC9E,kFAAkF;IAClF,sFAAsF;IACtF,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC;IAC1D,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC;IAC1D,IAAI,QAAsD,CAAC;IAC3D,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACpD,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,GAAG,KAAK,EAAE,CAAC;YAC9E,MAAM,IAAI,KAAK,CACb,4FAA4F,CAC7F,CAAC;QACJ,CAAC;QACD,QAAQ,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;IACD,MAAM,qBAAqB,GAAG,IAAI,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IACpE,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE;QACxC,YAAY;QACZ,YAAY,EAAE,gBAAgB,CAAC,MAAM;QACrC,SAAS,EAAE,iBAAiB;QAC5B,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,IAAI;QAClC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,IAAI;KACnC,CAAC,CAAC;IACH,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,CAAC;AACnI,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,gFAAgF;IAChF,iFAAiF;IACjF,8EAA8E;IAC9E,0BAA0B;IAC1B,MAAM,yBAAyB,GAAG,+BAA+B,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAE9E,kFAAkF;IAClF,oFAAoF;IACpF,mFAAmF;IACnF,mFAAmF;IACnF,kFAAkF;IAClF,4EAA4E;IAC5E,MAAM,QAAQ,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAE3C,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC;QAC/B,QAAQ;QACR,UAAU;QACV,YAAY;QACZ,cAAc,EAAE,eAAe;QAC/B,OAAO;QACP,MAAM;QACN,yBAAyB;QACzB,GAAG,QAAQ;KACZ,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAc,EAAiB,EAAE;QACvD,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,IAAI,CAAC;YACH,QAAQ,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;gBACzC,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE;oBACrC,MAAM,EAAE,SAAS;oBACjB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBACxD,CAAC,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE;gBACrC,MAAM,EAAE,SAAS;gBACjB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,IAAI,CAAC;YACH,QAAQ,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;gBACxC,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE;oBACrC,MAAM,EAAE,QAAQ;oBAChB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBACxD,CAAC,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE;gBACrC,MAAM,EAAE,QAAQ;gBAChB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,MAAM,CAAC,KAAK,CAAC,uBAAuB,EAAE;QACpC,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;KACxD,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"cello-daemon.js","sourceRoot":"","sources":["../../src/bin/cello-daemon.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EAIrB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAK/D,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAkB7G,OAAO,KAAK,EAAE,kBAAkB,EAA+C,MAAM,yBAAyB,CAAC;AAM/G,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAmInF,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,SAAS,IAAI,oBAAoB,CAAC;IAClC;;;;OAIG;IACH,qBAAqB,IAAI,kBAAkB,CAAC;IAC5C;;;;;;;;OAQG;IACH,gBAAgB,IAAI,SAAS,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,oBAAoB,IAAI,kBAAkB,CAAC;IAC3C;;;OAGG;IACH,iBAAiB,IAAI,eAAe,CAAC;CACtC;AAyCD,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA80G7E"}
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EAKrB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAK/D,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAoB7G,OAAO,KAAK,EAAE,kBAAkB,EAA+C,MAAM,yBAAyB,CAAC;AAM/G,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAmInF,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,SAAS,IAAI,oBAAoB,CAAC;IAClC;;;;OAIG;IACH,qBAAqB,IAAI,kBAAkB,CAAC;IAC5C;;;;;;;;OAQG;IACH,gBAAgB,IAAI,SAAS,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,oBAAoB,IAAI,kBAAkB,CAAC;IAC3C;;;OAGG;IACH,iBAAiB,IAAI,eAAe,CAAC;CACtC;AAyCD,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAk5G7E"}
package/dist/daemon.js CHANGED
@@ -40,8 +40,9 @@ import { createNode, SignalingManager } from "@cello-protocol/transport";
40
40
  import { createSignalingConnect } from "./signaling-connect.js";
41
41
  import { RegistrationManager } from "./registration-manager.js";
42
42
  import { DaemonRegistrationContext } from "./registration-context.js";
43
- import { FileRegistrationPersistence } from "./registration-persistence.js";
44
- import { verify as ed25519Verify, sealToRecipient } from "@cello-protocol/crypto";
43
+ import { DbRegistrationPersistence, DbIdentityStore } from "./db-identity-store.js";
44
+ import { DbManifestVersionStore } from "./manifest-version-store-db.js";
45
+ import { verify as ed25519Verify, sealToRecipient, generateKLocalSeed, InMemoryKeyProvider } from "@cello-protocol/crypto";
45
46
  import { attemptSealUpgrade as attemptSealUpgradeImpl, verifyUpgradeConfirmedCert } from "./seal-upgrade.js";
46
47
  // CELLO-M7-MSG-001 (AC-013/AC-018): the single application content-size cap, enforced
47
48
  // at the send point here (the receive point lives in the transport content decode).
@@ -169,7 +170,7 @@ class ProductionSessionNodeFactory {
169
170
  }
170
171
  }
171
172
  export async function startDaemon(config) {
172
- const { celloDir, socketPath, lockFilePath, maxConnections, version, logger, manifestProvider, manifestRootKeys, manifestThreshold, manifestVersionStore, manifestPollScheduler, signalingConnect, challengeVerifier, directoryEndpointResolver, sessionNodeFactory, sessionNegotiator, getRelayCircuitAddress, } = config;
173
+ const { celloDir, socketPath, lockFilePath, maxConnections, version, logger, manifestProvider, manifestRootKeys, manifestThreshold, manifestVersionStore: injectedManifestVersionStore, manifestPollScheduler, signalingConnect, challengeVerifier, directoryEndpointResolver, sessionNodeFactory, sessionNegotiator, getRelayCircuitAddress, } = config;
173
174
  // CELLO-M7-TRANSPORT-001: composition-root selection of the transport selector.
174
175
  // Driven by CELLO_ENV; fails fast at startup (here, not at first session) when a
175
176
  // production environment is missing the required transport dialer (AC-010).
@@ -183,6 +184,31 @@ export async function startDaemon(config) {
183
184
  env: celloEnv,
184
185
  selector: isProductionVariant(celloEnv) ? "real" : "stub",
185
186
  });
187
+ // ADV-006 + ADV-008 (hoisted — code-review MED): pure config validation runs BEFORE any disk side
188
+ // effect (lock, the irreversible one-time migration, DB open). A misconfigured daemon must fail
189
+ // before mutating state. If manifestProvider is set, manifestRootKeys + a positive threshold are
190
+ // required.
191
+ if (manifestProvider && (!manifestRootKeys || !manifestThreshold || manifestThreshold <= 0)) {
192
+ throw new Error("DaemonConfig: manifestProvider requires manifestRootKeys (non-empty) and manifestThreshold (positive integer >= 1)");
193
+ }
194
+ // ── PERSIST-002: open the encrypted store FIRST (runs the one-time flat-file → SQLCipher migration
195
+ // (AC-006) + creates the agents/manifest_state schema), under the single-instance lock. This must
196
+ // precede the manifest verification below because the manifest version is now stored in the
197
+ // encrypted DB (AC-008), not a manifest-version.json file. ──
198
+ await mkdir(celloDir, { recursive: true });
199
+ await mkdir(dirname(socketPath), { recursive: true });
200
+ await acquireLock(lockFilePath, { pid: process.pid, socketPath, version });
201
+ const sessionNodeManager = new SessionNodeManager({
202
+ factory: sessionNodeFactory ?? new ProductionSessionNodeFactory(),
203
+ logger,
204
+ dbPath: join(celloDir, "sessions.db"),
205
+ contentTtfMs: config.contentTtfMs,
206
+ autoNatProbers: () => [],
207
+ });
208
+ await sessionNodeManager.initialize();
209
+ // AC-008: the manifest version store is DB-backed by default (encrypted manifest_state table). A
210
+ // test may inject an override (e.g. InMemoryManifestVersionStore) via config.
211
+ const manifestVersionStore = injectedManifestVersionStore ?? new DbManifestVersionStore(sessionNodeManager.getDb(), logger);
186
212
  // M7-MANIFEST-002: Load and verify consortium manifest BEFORE any directory connection.
187
213
  //
188
214
  // Pseudocode for manifest loading:
@@ -197,12 +223,6 @@ export async function startDaemon(config) {
197
223
  // M7 Keystone: the version of the verified manifest, surfaced in ConnectResult.
198
224
  // Stays 0 when no manifestProvider is configured (the M6 backward-compat path).
199
225
  let verifiedManifestVersion = 0;
200
- // ADV-006 + ADV-008: If manifestProvider is set, manifestRootKeys and a positive
201
- // manifestThreshold are required. Fail loudly on misconfiguration rather than
202
- // silently proceeding unverified.
203
- if (manifestProvider && (!manifestRootKeys || !manifestThreshold || manifestThreshold <= 0)) {
204
- throw new Error("DaemonConfig: manifestProvider requires manifestRootKeys (non-empty) and manifestThreshold (positive integer >= 1)");
205
- }
206
226
  if (manifestProvider && manifestRootKeys && manifestThreshold !== undefined) {
207
227
  try {
208
228
  const manifest = await manifestProvider.loadAndVerify(manifestRootKeys, manifestThreshold);
@@ -223,26 +243,16 @@ export async function startDaemon(config) {
223
243
  });
224
244
  }
225
245
  else {
226
- // Check version monotonicity if version store is provided
227
- if (manifestVersionStore) {
228
- const lastSeen = await manifestVersionStore.getLastSeenVersion();
229
- if (lastSeen !== null && manifest.version < lastSeen) {
230
- logger.error("directory.auth.manifest.version.rollback", {
231
- manifestVersion: manifest.version,
232
- lastSeenVersion: lastSeen,
233
- });
234
- }
235
- else {
236
- await manifestVersionStore.persistVersion(manifest.version);
237
- manifestVerified = true;
238
- verifiedManifestVersion = manifest.version;
239
- logger.info("directory.auth.manifest.verified", {
240
- manifestVersion: manifest.version,
241
- signerCount: manifest.signatures.length,
242
- });
243
- }
246
+ // Anti-rollback monotonicity (manifestVersionStore is always present now — DB-backed default).
247
+ const lastSeen = await manifestVersionStore.getLastSeenVersion();
248
+ if (lastSeen !== null && manifest.version < lastSeen) {
249
+ logger.error("directory.auth.manifest.version.rollback", {
250
+ manifestVersion: manifest.version,
251
+ lastSeenVersion: lastSeen,
252
+ });
244
253
  }
245
254
  else {
255
+ await manifestVersionStore.persistVersion(manifest.version);
246
256
  manifestVerified = true;
247
257
  verifiedManifestVersion = manifest.version;
248
258
  logger.info("directory.auth.manifest.verified", {
@@ -262,21 +272,24 @@ export async function startDaemon(config) {
262
272
  // failed, the daemon must refuse to proceed. Operators who configure
263
273
  // manifestProvider have opted into manifest enforcement.
264
274
  if (manifestProvider && !manifestVerified) {
275
+ // code-review MED: this refuse now runs AFTER the DB is open + the lock is held (the DB had to
276
+ // open for the anti-rollback check). Release both before rethrowing so an in-process caller does
277
+ // not leak the DB handle / lock (in production the process exits, but be tidy).
278
+ try {
279
+ sessionNodeManager.getDb().close();
280
+ }
281
+ catch { /* ignore */ }
282
+ await removeLock(lockFilePath, logger).catch(() => { });
265
283
  throw new Error("Manifest verification failed. The daemon cannot start with an unverified manifest when manifestProvider is configured. " +
266
284
  "Check the logs for the specific failure reason (manifest_signature_invalid, manifest_expired, or manifest_version_rollback).");
267
285
  }
268
- // Ensure the cello directory exists
269
- await mkdir(celloDir, { recursive: true });
270
- // Ensure the socket parent directory exists
271
- await mkdir(dirname(socketPath), { recursive: true });
272
- // Load agent identities
273
- const { loaded: loadedAgents, failed: failedAgents } = await loadAgents(celloDir, logger);
274
- // Acquire lock file
275
- await acquireLock(lockFilePath, {
276
- pid: process.pid,
277
- socketPath,
278
- version,
279
- });
286
+ // Load agent identities from the encrypted `agents` table (PERSIST-002 AC-007 — one path).
287
+ // (The encrypted store + lock were established above, before manifest verification.)
288
+ const { loaded: loadedAgents, failed: failedAgents } = await loadAgents(sessionNodeManager.getDb(), logger);
289
+ // PERSIST-002: per-agent DB-backed identity persistence. The registration handler and the
290
+ // ceremony/seal signer-reconstruction load the FROST share (and persist the identity) through this
291
+ // seam the encrypted `agents` row, never a flat file.
292
+ const getPersistence = (agentName) => new DbRegistrationPersistence({ db: sessionNodeManager.getDb(), agentName, logger });
280
293
  // Build agent state (all start in 'registered' state — no auto-start)
281
294
  const agents = [
282
295
  ...loadedAgents.map((a) => ({
@@ -446,7 +459,7 @@ export async function startDaemon(config) {
446
459
  // registration routing). Unregistered implicitly when the manager is stopped.
447
460
  wireSessionCeremonyHandler({
448
461
  agentName,
449
- agentDir: join(celloDir, "agents", agentName),
462
+ persistence: getPersistence(agentName),
450
463
  agentPubkeyHex,
451
464
  getNode: entry.getNode,
452
465
  getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
@@ -456,7 +469,7 @@ export async function startDaemon(config) {
456
469
  // DOD-SPINE-7: coordinate the SEAL FROST ceremony on this agent's stream too.
457
470
  wireSealCeremonyHandler({
458
471
  agentName,
459
- agentDir: join(celloDir, "agents", agentName),
472
+ persistence: getPersistence(agentName),
460
473
  agentPubkeyHex,
461
474
  getNode: entry.getNode,
462
475
  getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
@@ -511,7 +524,7 @@ export async function startDaemon(config) {
511
524
  if (primaryAgent) {
512
525
  wireSessionCeremonyHandler({
513
526
  agentName: primaryAgent.name,
514
- agentDir: join(celloDir, "agents", primaryAgent.name),
527
+ persistence: getPersistence(primaryAgent.name),
515
528
  agentPubkeyHex: primaryAgent.pubkey,
516
529
  getNode: getDirectoryNode,
517
530
  getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
@@ -521,7 +534,7 @@ export async function startDaemon(config) {
521
534
  // DOD-SPINE-7: the primary agent also coordinates the SEAL FROST ceremony on the keystone.
522
535
  wireSealCeremonyHandler({
523
536
  agentName: primaryAgent.name,
524
- agentDir: join(celloDir, "agents", primaryAgent.name),
537
+ persistence: getPersistence(primaryAgent.name),
525
538
  agentPubkeyHex: primaryAgent.pubkey,
526
539
  getNode: getDirectoryNode,
527
540
  getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
@@ -540,22 +553,9 @@ export async function startDaemon(config) {
540
553
  const perConnectionState = new Map();
541
554
  // Set of agents currently in "online" state (transitioned via cello_start_agent)
542
555
  const onlineAgents = new Set();
543
- // Initialize SessionNodeManager (DAEMON-002: composition root AC-011).
544
- // This runs before the IPC socket opens so:
545
- // 1. The standing receiver is ready before any cello_await_session call.
546
- // 2. Interrupted session detection runs before any tool call can race.
547
- const sessionNodeManager = new SessionNodeManager({
548
- factory: sessionNodeFactory ?? new ProductionSessionNodeFactory(),
549
- logger,
550
- dbPath: join(celloDir, "sessions.db"),
551
- contentTtfMs: config.contentTtfMs,
552
- // CELLO-M7-TRANSPORT-001: directory-node AutoNAT probers (SI-002). The
553
- // directory connection (SIGNAL-001) is not yet wired into the daemon, so the
554
- // prober set is empty — AutoNAT cannot run and the standing receiver reports
555
- // the conservative default + transport.autonat.unavailable (AC-004/DB-001).
556
- autoNatProbers: () => [],
557
- });
558
- await sessionNodeManager.initialize();
556
+ // SessionNodeManager was constructed + initialized at the top of startDaemon (PERSIST-002 — the
557
+ // encrypted store must open before agents load from the `agents` table). Its standing receiver +
558
+ // interrupted-session detection are already ready here, before the IPC socket opens.
559
559
  // CELLO-M7-TRANSPORT-001: the daemon's runtime AutoNAT service is the one
560
560
  // wrapping the standing receiver node (it emits transport.autonat.result /
561
561
  // transport.autonat.unavailable and its dialability drives the SessionAssignment
@@ -687,7 +687,7 @@ export async function startDaemon(config) {
687
687
  // DAEMON-003: Initialize RetryQueue and NonceDedupStore (AC-008).
688
688
  // Both use the same SQLite DB as the SessionNodeManager (daemon.db equivalent).
689
689
  // loadFromDb() must complete BEFORE IPC socket opens (AC-007).
690
- const retryQueue = new RetryQueue(sessionNodeManager.getDb(), logger, sessionNodeManager.getTranscriptCipher());
690
+ const retryQueue = new RetryQueue(sessionNodeManager.getDb(), logger);
691
691
  retryQueue.loadFromDb();
692
692
  // CELLO-M7-MSG-001 (AC-001/AC-003/AC-019): wire the awaiting-ACK lifecycle's durable
693
693
  // side effects to the retry_queue. A `persisted` delivery ACK clears the durable
@@ -925,6 +925,41 @@ export async function startDaemon(config) {
925
925
  notificationDispatcher.dispatchAgentStateChanged(name, "online", "started");
926
926
  return { ok: true };
927
927
  });
928
+ // ─── PERSIST-002 (AC-004): cello_create_agent handler ───
929
+ // The explicit agent-creation path: generate a fresh K_local seed, write it as an `agents` row in
930
+ // the encrypted DB (NO key file), and wire the agent into the live daemon so it can be registered
931
+ // and used WITHOUT a restart. Creation is explicit — cello_start_agent never auto-creates on a typo.
932
+ handlers.set("cello_create_agent", async (params, _connectionId) => {
933
+ const name = params?.name;
934
+ if (!name || typeof name !== "string" || !/^[a-zA-Z0-9_-]{1,64}$/.test(name)) {
935
+ return { ok: false, reason: "invalid_agent_name", guidance: "Provide a 'name' (1-64 chars: letters, digits, '-' or '_') for the new agent." };
936
+ }
937
+ const store = new DbIdentityStore(sessionNodeManager.getDb(), logger);
938
+ if (store.hasAgent(name) || agents.some((a) => a.name === name)) {
939
+ return { ok: false, reason: "agent_already_exists", guidance: `Agent '${name}' already exists. Choose a different name, or see cello_list_agents.` };
940
+ }
941
+ let pubkeyHex;
942
+ try {
943
+ const seed = generateKLocalSeed();
944
+ const keyProvider = new InMemoryKeyProvider(seed);
945
+ pubkeyHex = Buffer.from(await keyProvider.getPublicKey()).toString("hex");
946
+ // SI-001: createAgent stores the seed in the encrypted DB and logs only the pubkey.
947
+ store.createAgent(name, seed, pubkeyHex);
948
+ // Runtime-add: make the agent immediately registrable/usable (the register handler resolves
949
+ // identity from keyProviders/loadedAgents; per-agent signaling is created lazily on register).
950
+ keyProviders.set(name, keyProvider);
951
+ loadedAgents.push({ name, pubkey: pubkeyHex, keyProvider });
952
+ agents.push({ name, state: "registered", pubkey: pubkeyHex });
953
+ }
954
+ catch (err) {
955
+ logger.error("persist.identity.persist.failed", { agentName: name, error: err instanceof Error ? err.message : String(err) });
956
+ return { ok: false, reason: "agent_create_failed", guidance: "Could not create the agent. Check the daemon log and that the CELLO directory is writable, then retry." };
957
+ }
958
+ // Creation is not an online/offline transition — the agent appears (cello_list_agents) but is
959
+ // not online until cello_start_agent. Just record it.
960
+ logger.info("agent.created", { agentName: name, agentPubkey: pubkeyHex });
961
+ return { ok: true, name, pubkey: pubkeyHex };
962
+ });
928
963
  // ─── MCP-001: cello_stop_agent handler ───
929
964
  handlers.set("cello_stop_agent", async (params, _connectionId) => {
930
965
  const name = params?.name;
@@ -1028,7 +1063,7 @@ export async function startDaemon(config) {
1028
1063
  }
1029
1064
  const keyProvider = keyProviders.get(name);
1030
1065
  if (!keyProvider) {
1031
- return { ok: false, reason: "agent_not_found", guidance: `Agent '${name}' has no local K_local key loaded. Its key must exist at ~/.cello/agents/${name}/key before registration create it and restart the daemon, then retry cello_register.` };
1066
+ return { ok: false, reason: "agent_not_found", guidance: `Agent '${name}' does not exist. Create it first with cello_create_agent('${name}') (or 'cello create-agent ${name}'), then retry cello_register.` };
1032
1067
  }
1033
1068
  if (!directoryEndpointResolver) {
1034
1069
  return { ok: false, reason: "directory_unreachable", guidance: "The daemon has no directory endpoint resolver configured, so it cannot reach the directory to register." };
@@ -1074,7 +1109,7 @@ export async function startDaemon(config) {
1074
1109
  guidance: `Agent '${name}' could not establish its directory signaling stream within 10s. Check CELLO_DIRECTORY_URL and that the directory is reachable, then retry cello_register.`,
1075
1110
  };
1076
1111
  }
1077
- const persistence = new FileRegistrationPersistence({ agentDir: join(celloDir, "agents", name), logger });
1112
+ const persistence = getPersistence(name);
1078
1113
  const ctx = new DaemonRegistrationContext({
1079
1114
  signaling: agentSignaling,
1080
1115
  getDirectoryNode: agentGetNode,
@@ -1092,6 +1127,10 @@ export async function startDaemon(config) {
1092
1127
  await dropAgentSignaling(name);
1093
1128
  return { ok: false, reason: result.error, guidance: registrationGuidance(result.error) };
1094
1129
  }
1130
+ // PERSIST-002 (AC-013): the identity row (K_local + share + ML-DSA + registration) is durably
1131
+ // committed at this point (RegistrationManager awaits the persist before returning success).
1132
+ // SI-001: never log a secret — only the agent name + PUBLIC key.
1133
+ logger.info("persist.identity.persisted", { agentName: name, agentPubkey: agentPubkeyHex });
1095
1134
  // Capture-now-or-lose-it: persist the agent→user link (using it is future
1096
1135
  // trust-layer work). L1: the agent is already registered at this point —
1097
1136
  // a link-write failure must NOT be reported as a registration failure.
@@ -1168,7 +1207,7 @@ export async function startDaemon(config) {
1168
1207
  return;
1169
1208
  }
1170
1209
  const record = sessionNodeManager.getSessionRecord(agentName, sidHex);
1171
- const verdict = await verifyBilateralSealCertificate({ agentDir: join(celloDir, "agents", agentName), agentPubkeyHex, logger, counterpartyPrimaryHex: record?.counterparty_primary_pubkey ?? null }, {
1210
+ const verdict = await verifyBilateralSealCertificate({ persistence: getPersistence(agentName), agentPubkeyHex, logger, counterpartyPrimaryHex: record?.counterparty_primary_pubkey ?? null }, {
1172
1211
  sessionId: sessionIdBytes,
1173
1212
  sealedRoot: sealedRootBytes,
1174
1213
  leafCount,
@@ -1318,7 +1357,7 @@ export async function startDaemon(config) {
1318
1357
  waiter({ ok: false, reason: "malformed_certificate" });
1319
1358
  return;
1320
1359
  }
1321
- const result = await verifyUnilateralCertificate({ agentDir: join(celloDir, "agents", agentName), agentPubkeyHex, logger }, { sessionId, sealedRoot, leafCount, closeTimestamp: closeTs, frostSignature: frostSig, signatureType: sigType });
1360
+ const result = await verifyUnilateralCertificate({ persistence: getPersistence(agentName), agentPubkeyHex, logger }, { sessionId, sealedRoot, leafCount, closeTimestamp: closeTs, frostSignature: frostSig, signatureType: sigType });
1322
1361
  pendingUnilateralWaiters.delete(sidHex);
1323
1362
  if (!result.ok) {
1324
1363
  // SI-003: do NOT mark sealed when the certificate signature does not verify.
@@ -1374,7 +1413,7 @@ export async function startDaemon(config) {
1374
1413
  // ONLY on ok. Never trust the directory's "bilateral" claim.
1375
1414
  async function verifyAndApplyUpgradeConfirmed(agentName, agentPubkeyHex, sidHex, frame) {
1376
1415
  const result = await verifyUpgradeConfirmedCert({
1377
- logger, agentName, agentPubkeyHex, celloDir,
1416
+ logger, agentName, agentPubkeyHex, persistence: getPersistence(agentName),
1378
1417
  getCounterpartyHex: (a, s) => sessionNodeManager.getSessionRecord(a, s)?.counterparty_pubkey ?? null,
1379
1418
  }, sidHex, frame);
1380
1419
  if (!result.ok)
@@ -1435,7 +1474,6 @@ export async function startDaemon(config) {
1435
1474
  // no_current_agent guard.
1436
1475
  const SESSION_TOOLS_REQUIRING_AGENT = [
1437
1476
  "cello_receive_session",
1438
- "cello_list_sessions",
1439
1477
  ];
1440
1478
  const NO_CURRENT_AGENT_RESPONSE = {
1441
1479
  ok: false,
@@ -1825,6 +1863,35 @@ export async function startDaemon(config) {
1825
1863
  // so the reader can tell a real gap from an empty transcript.
1826
1864
  return { ok: true, session_id: sessionId, messages, undecryptable };
1827
1865
  });
1866
+ // cello_list_sessions: the discovery surface — every persisted session for the
1867
+ // current agent (active, interrupted, sealed, seal_interrupted_pending), newest
1868
+ // updated first. This is where cello_get_transcript / cello_get_sealed_receipt
1869
+ // get their session ids; without it those by-id reads have no starting point,
1870
+ // and the guidance strings that point here ("See cello_list_sessions") dead-end.
1871
+ // Read from the persisted SQLite store, so it works after a daemon restart and
1872
+ // from a fresh MCP connection (no in-memory session-node required).
1873
+ handlers.set("cello_list_sessions", async (_params, connectionId) => {
1874
+ const connState = perConnectionState.get(connectionId);
1875
+ if (!connState || !connState.currentAgent) {
1876
+ return NO_CURRENT_AGENT_RESPONSE;
1877
+ }
1878
+ const agentName = connState.currentAgent;
1879
+ const sessions = sessionNodeManager
1880
+ .getSessionsForAgent(agentName)
1881
+ .map((row) => ({
1882
+ sessionId: row.session_id,
1883
+ agentName: row.agent_name,
1884
+ counterpartyPubkey: row.counterparty_pubkey,
1885
+ status: row.status,
1886
+ messageCount: row.message_count ?? 0,
1887
+ createdAt: new Date(row.created_at).toISOString(),
1888
+ updatedAt: new Date(row.updated_at).toISOString(),
1889
+ // interrupted_at is the canonical ISO interruption stamp; null for any
1890
+ // session that was never interrupted (active/sealed).
1891
+ interruptedAt: row.interrupted_at ?? null,
1892
+ }));
1893
+ return { ok: true, sessions };
1894
+ });
1828
1895
  // DAEMON-003 IPC handlers: queue_failed_send and check_nonce (AC-010)
1829
1896
  handlers.set("queue_failed_send", async (params, _connectionId) => {
1830
1897
  const sessionId = params?.sessionId;