@cello-protocol/daemon 0.0.8 → 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 (64) 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 +104 -65
  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 +10 -10
  49. package/dist/session-node-manager.d.ts.map +1 -1
  50. package/dist/session-node-manager.js +45 -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/package.json +5 -4
  57. package/dist/manifest-version-store-file.d.ts +0 -18
  58. package/dist/manifest-version-store-file.d.ts.map +0 -1
  59. package/dist/manifest-version-store-file.js +0 -40
  60. package/dist/manifest-version-store-file.js.map +0 -1
  61. package/dist/transcript-cipher.d.ts +0 -31
  62. package/dist/transcript-cipher.d.ts.map +0 -1
  63. package/dist/transcript-cipher.js +0 -74
  64. package/dist/transcript-cipher.js.map +0 -1
@@ -0,0 +1,235 @@
1
+ /**
2
+ * CELLO-M7-PERSIST-002 — DB-backed identity store (the `agents` table).
3
+ *
4
+ * Every piece of an agent's persisted identity — the K_local Ed25519 seed, the ML-DSA keypair, the
5
+ * FROST signing share, the registration record, and the agent↔user link — lives as ONE row of the
6
+ * `agents` table in the SQLCipher-encrypted daemon DB. This replaces the per-agent plaintext flat
7
+ * files (`agents/<name>/key`, `frost-share.json`, `ml-dsa-keypair.json`, `registration-state.json`,
8
+ * `agent-user-link.json`).
9
+ *
10
+ * `DbRegistrationPersistence` implements the SAME `DaemonRegistrationPersistence` interface the
11
+ * daemon's RegistrationManager and the ceremony/seal signer-reconstruction already consume — so the
12
+ * call sites change only their construction (a DB handle + agent name instead of an `agentDir`), not
13
+ * their behaviour. Writes are AWAITED single-row UPSERTs (SI-003: a register-success implies a
14
+ * durably committed row — fixing the old fire-and-forget at registration-manager.ts:229).
15
+ *
16
+ * The K_local seed and FROST/ML-DSA secrets are stored as BLOB columns inside the encrypted DB; they
17
+ * are NEVER written to a flat file and NEVER logged (SI-001). The row is the only home.
18
+ */
19
+ const ML_DSA_ALGORITHM = "ML-DSA-44";
20
+ /**
21
+ * Idempotent schema for the identity store. Called once at daemon init AND defensively by each
22
+ * DbRegistrationPersistence constructor (CREATE TABLE IF NOT EXISTS is idempotent), so the store
23
+ * works whether or not the composition root has run its own ensure.
24
+ */
25
+ export function ensureIdentitySchema(db) {
26
+ db.exec(`
27
+ CREATE TABLE IF NOT EXISTS agents (
28
+ agent_name TEXT PRIMARY KEY,
29
+ -- K_local Ed25519 identity (PERSIST-002: seed moves from agents/<name>/key into this BLOB).
30
+ k_local_seed BLOB NOT NULL,
31
+ k_local_pubkey TEXT NOT NULL,
32
+ -- lifecycle: 'created' (K_local exists, not yet registered) | 'registered'.
33
+ state TEXT NOT NULL DEFAULT 'created',
34
+ -- ML-DSA keypair (was ml-dsa-keypair.json).
35
+ ml_dsa_pubkey TEXT,
36
+ ml_dsa_secret BLOB,
37
+ ml_dsa_algorithm TEXT,
38
+ -- FROST signing share (was frost-share.json).
39
+ frost_epoch_id TEXT,
40
+ frost_primary_pubkey TEXT,
41
+ frost_identifier TEXT,
42
+ frost_signing_share BLOB,
43
+ frost_threshold INTEGER,
44
+ frost_participants INTEGER,
45
+ frost_commitments BLOB,
46
+ frost_verifying_shares BLOB,
47
+ frost_dkg_method TEXT,
48
+ -- registration record (was registration-state.json).
49
+ reg_agent_id TEXT,
50
+ reg_primary_pubkey TEXT,
51
+ reg_ml_dsa_pubkey TEXT,
52
+ reg_registered_at INTEGER,
53
+ reg_status TEXT,
54
+ -- agent↔user link captured at registration (was agent-user-link.json).
55
+ link_agent_id TEXT,
56
+ link_pre_auth_token TEXT,
57
+ link_linked_at INTEGER,
58
+ created_at INTEGER NOT NULL,
59
+ updated_at INTEGER NOT NULL
60
+ )
61
+ `);
62
+ }
63
+ const toBuf = (b) => Buffer.from(b);
64
+ const toBytes = (v) => v instanceof Uint8Array ? new Uint8Array(v) : Buffer.isBuffer(v) ? new Uint8Array(v) : new Uint8Array(0);
65
+ export class DbIdentityStore {
66
+ #db;
67
+ #logger;
68
+ constructor(db, logger) {
69
+ this.#db = db;
70
+ this.#logger = logger;
71
+ ensureIdentitySchema(db);
72
+ }
73
+ /** True if an agent row with this name already exists. */
74
+ hasAgent(agentName) {
75
+ const row = this.#db.prepare("SELECT 1 AS one FROM agents WHERE agent_name = ?").get(agentName);
76
+ return row !== undefined;
77
+ }
78
+ /**
79
+ * Create a new agent row holding a fresh K_local seed (AC-004). Explicit only — callers generate
80
+ * the seed (crypto) and pass it in. Throws if the name already exists (no silent overwrite of an
81
+ * identity).
82
+ */
83
+ createAgent(agentName, kLocalSeed, kLocalPubkeyHex) {
84
+ if (this.hasAgent(agentName)) {
85
+ throw new Error(`agent '${agentName}' already exists`);
86
+ }
87
+ const now = Date.now();
88
+ this.#db
89
+ .prepare(`INSERT INTO agents (agent_name, k_local_seed, k_local_pubkey, state, created_at, updated_at)
90
+ VALUES (?, ?, ?, 'created', ?, ?)`)
91
+ .run(agentName, toBuf(kLocalSeed), kLocalPubkeyHex, now, now);
92
+ // SI-001: the seed is NEVER logged — only the agent name and PUBLIC key.
93
+ this.#logger.info("persist.identity.created", { agentName, agentPubkey: kLocalPubkeyHex });
94
+ }
95
+ /** Enumerate all agents (name + seed + pubkey + state) for the daemon's startup loader. */
96
+ listAgents() {
97
+ const rows = this.#db
98
+ .prepare("SELECT agent_name, k_local_seed, k_local_pubkey, state FROM agents ORDER BY agent_name ASC")
99
+ .all();
100
+ return rows.map((r) => ({
101
+ agentName: r.agent_name,
102
+ kLocalSeed: toBytes(r.k_local_seed),
103
+ kLocalPubkey: r.k_local_pubkey,
104
+ state: r.state,
105
+ }));
106
+ }
107
+ }
108
+ /**
109
+ * DB-backed `DaemonRegistrationPersistence`. Scoped to a single agent's row. All persist operations
110
+ * are single-row UPSERTs that update the named agent's row; a register-success therefore implies the
111
+ * material is durably committed (SI-003). A persist against a non-existent agent throws — the row is
112
+ * created by the agent-creation path (AC-004) before registration runs.
113
+ */
114
+ export class DbRegistrationPersistence {
115
+ #db;
116
+ #agentName;
117
+ #logger;
118
+ constructor(opts) {
119
+ this.#db = opts.db;
120
+ this.#agentName = opts.agentName;
121
+ this.#logger = opts.logger;
122
+ ensureIdentitySchema(opts.db);
123
+ }
124
+ #updateRow(setClause, params) {
125
+ const now = Date.now();
126
+ const res = this.#db
127
+ .prepare(`UPDATE agents SET ${setClause}, updated_at = ? WHERE agent_name = ?`)
128
+ .run(...params, now, this.#agentName);
129
+ if (Number(res.changes) === 0) {
130
+ // The agent row must exist (created by the agent-creation path before registration). A missing
131
+ // row is a real fault, not something to paper over — fail loud so registration fails (AC-012).
132
+ throw new Error(`identity_persist_failed: no agent row for '${this.#agentName}'`);
133
+ }
134
+ }
135
+ async persistMlDsaKeypair(opts) {
136
+ // SI-001: secretKeyBlob is written to the encrypted DB only — never logged.
137
+ this.#updateRow("ml_dsa_pubkey = ?, ml_dsa_secret = ?, ml_dsa_algorithm = ?", [
138
+ opts.mlDsaPubkey,
139
+ toBuf(opts.secretKeyBlob),
140
+ ML_DSA_ALGORITHM,
141
+ ]);
142
+ this.#logger.info("registration.mldsa.persisted", { mlDsaPubkey: opts.mlDsaPubkey });
143
+ }
144
+ async persistRegistrationState(opts) {
145
+ this.#updateRow("reg_agent_id = ?, reg_primary_pubkey = ?, reg_ml_dsa_pubkey = ?, reg_registered_at = ?, reg_status = 'active', state = 'registered'", [opts.agentId, opts.primaryPubkey, opts.mlDsaPubkey, opts.registeredAt]);
146
+ this.#logger.info("registration.state.persisted", {
147
+ agentId: opts.agentId,
148
+ primaryPubkey: opts.primaryPubkey,
149
+ });
150
+ }
151
+ async persistFrostKeyShare(opts) {
152
+ // SI-001: signingShare is written to the encrypted DB only — never logged.
153
+ this.#updateRow(`frost_epoch_id = ?, frost_primary_pubkey = ?, frost_identifier = ?, frost_signing_share = ?,
154
+ frost_threshold = ?, frost_participants = ?, frost_commitments = ?, frost_verifying_shares = ?,
155
+ frost_dkg_method = ?`, [
156
+ opts.epochId,
157
+ opts.primaryPubkey,
158
+ opts.identifier,
159
+ toBuf(opts.signingShare),
160
+ opts.threshold,
161
+ opts.participants,
162
+ toBuf(opts.commitmentsCbor),
163
+ toBuf(opts.verifyingSharesCbor),
164
+ opts.dkgMethod,
165
+ ]);
166
+ this.#logger.info("registration.frost.share.persisted", {
167
+ epochId: opts.epochId,
168
+ threshold: opts.threshold,
169
+ participants: opts.participants,
170
+ dkgMethod: opts.dkgMethod,
171
+ });
172
+ }
173
+ async persistAgentUserLink(opts) {
174
+ // SI: the preAuthToken is a bearer ticket — written to the encrypted DB only, never logged.
175
+ this.#updateRow("link_agent_id = ?, link_pre_auth_token = ?, link_linked_at = ?", [
176
+ opts.agentId,
177
+ opts.preAuthToken,
178
+ opts.linkedAt,
179
+ ]);
180
+ this.#logger.info("registration.user_link.persisted", { agentId: opts.agentId });
181
+ }
182
+ // ─── Load (restart rehydration) ──────────────────────────────────────────────
183
+ #row() {
184
+ return this.#db.prepare("SELECT * FROM agents WHERE agent_name = ?").get(this.#agentName);
185
+ }
186
+ async loadRegistrationState() {
187
+ const r = this.#row();
188
+ if (!r || r["reg_agent_id"] == null)
189
+ return null;
190
+ return {
191
+ agentId: String(r["reg_agent_id"]),
192
+ primaryPubkey: String(r["reg_primary_pubkey"]),
193
+ mlDsaPubkey: String(r["reg_ml_dsa_pubkey"]),
194
+ registeredAt: Number(r["reg_registered_at"]),
195
+ status: String(r["reg_status"]),
196
+ };
197
+ }
198
+ async loadMlDsaKeypair() {
199
+ const r = this.#row();
200
+ if (!r || r["ml_dsa_secret"] == null)
201
+ return null;
202
+ return {
203
+ mlDsaPubkey: String(r["ml_dsa_pubkey"]),
204
+ secretKeyBlob: toBytes(r["ml_dsa_secret"]),
205
+ algorithm: String(r["ml_dsa_algorithm"]),
206
+ };
207
+ }
208
+ async loadActiveFrostKeyShare() {
209
+ const r = this.#row();
210
+ if (!r || r["frost_signing_share"] == null)
211
+ return null;
212
+ return {
213
+ epochId: String(r["frost_epoch_id"]),
214
+ primaryPubkey: String(r["frost_primary_pubkey"]),
215
+ identifier: String(r["frost_identifier"]),
216
+ signingShare: toBytes(r["frost_signing_share"]),
217
+ threshold: Number(r["frost_threshold"]),
218
+ participants: Number(r["frost_participants"]),
219
+ commitmentsCbor: toBytes(r["frost_commitments"]),
220
+ verifyingSharesCbor: toBytes(r["frost_verifying_shares"]),
221
+ dkgMethod: String(r["frost_dkg_method"]),
222
+ };
223
+ }
224
+ async loadAgentUserLink() {
225
+ const r = this.#row();
226
+ if (!r || r["link_agent_id"] == null)
227
+ return null;
228
+ return {
229
+ agentId: String(r["link_agent_id"]),
230
+ preAuthToken: String(r["link_pre_auth_token"]),
231
+ linkedAt: Number(r["link_linked_at"]),
232
+ };
233
+ }
234
+ }
235
+ //# sourceMappingURL=db-identity-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-identity-store.js","sourceRoot":"","sources":["../src/db-identity-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * CELLO-M7-PERSIST-002 — Unit 4/6: one-time migration of pre-story flat-file state and a plaintext
3
+ * node:sqlite DB into the single SQLCipher-encrypted daemon DB (AC-006, DB-002).
4
+ *
5
+ * Before this story the daemon kept:
6
+ * - identity as plaintext flat files: `agents/<name>/key` (37-byte CELLO seed), `frost-share.json`,
7
+ * `ml-dsa-keypair.json`, `registration-state.json`, `agent-user-link.json`, and the legacy
8
+ * single-file `~/.cello/key` (loaded as agent "default");
9
+ * - sessions/transcript/etc. in a PLAINTEXT `sessions.db` (node:sqlite), with two columns
10
+ * (transcript.blob, retry_queue.content_blob) envelope-encrypted by a sibling
11
+ * `sessions.db.transcript-key`.
12
+ *
13
+ * This migration runs at daemon startup BEFORE the encrypted DB is opened. If it detects either a
14
+ * plaintext DB or any flat-file identity, it builds a fresh SQLCipher DB (at `<dbPath>.migrating`),
15
+ * imports every row of the old DB (decrypting the two column-ciphered blobs to plaintext) AND every
16
+ * flat-file identity item into `agents` rows, verifies, then atomically swaps it into place — backing
17
+ * up the old plaintext DB to `<dbPath>.pre-sqlcipher.bak` and deleting the flat files. It is
18
+ * idempotent: once the DB is encrypted and the flat files are gone, it is a no-op.
19
+ *
20
+ * Fail-closed (SI-002/DB-002): on ANY error it aborts cleanly — the original flat files / plaintext
21
+ * DB are left in place, the partial `.migrating` DB is discarded, and the caller surfaces
22
+ * `identity_migration_failed`. No identity is lost and no plaintext is left half-migrated.
23
+ */
24
+ import type { Logger } from "./types.js";
25
+ export interface MigrationResult {
26
+ migrated: boolean;
27
+ agentsMigrated: number;
28
+ rowsMigrated: number;
29
+ }
30
+ export declare class IdentityMigrationError extends Error {
31
+ readonly code: "identity_migration_failed";
32
+ readonly guidance: string;
33
+ constructor(message: string, guidance: string);
34
+ }
35
+ /**
36
+ * Run the one-time migration if needed. Returns { migrated:false } when there is nothing to do
37
+ * (fresh install or an already-encrypted DB with no flat files).
38
+ */
39
+ export declare function migrateToEncryptedIfNeeded(dbPath: string, logger: Logger): MigrationResult;
40
+ //# sourceMappingURL=identity-migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-migration.d.ts","sourceRoot":"","sources":["../src/identity-migration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAuBH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,IAAI,EAAG,2BAA2B,CAAU;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBACd,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAK9C;AA+ED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CAqJ1F"}