@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.
- package/dist/agent-loader.d.ts +15 -18
- package/dist/agent-loader.d.ts.map +1 -1
- package/dist/agent-loader.js +24 -81
- package/dist/agent-loader.js.map +1 -1
- package/dist/bin/cello-daemon.js +5 -7
- package/dist/bin/cello-daemon.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +104 -65
- package/dist/daemon.js.map +1 -1
- package/dist/db-identity-store.d.ts +97 -0
- package/dist/db-identity-store.d.ts.map +1 -0
- package/dist/db-identity-store.js +235 -0
- package/dist/db-identity-store.js.map +1 -0
- package/dist/identity-migration.d.ts +40 -0
- package/dist/identity-migration.d.ts.map +1 -0
- package/dist/identity-migration.js +455 -0
- package/dist/identity-migration.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest-version-store-db.d.ts +24 -0
- package/dist/manifest-version-store-db.d.ts.map +1 -0
- package/dist/manifest-version-store-db.js +57 -0
- package/dist/manifest-version-store-db.js.map +1 -0
- package/dist/manifest-version-store.d.ts +3 -1
- package/dist/manifest-version-store.d.ts.map +1 -1
- package/dist/manifest-version-store.js +3 -1
- package/dist/manifest-version-store.js.map +1 -1
- package/dist/nonce-dedup.d.ts +2 -2
- package/dist/nonce-dedup.d.ts.map +1 -1
- package/dist/nonce-dedup.js.map +1 -1
- package/dist/registration-manager.d.ts.map +1 -1
- package/dist/registration-manager.js +78 -44
- package/dist/registration-manager.js.map +1 -1
- package/dist/retry-queue.d.ts +2 -3
- package/dist/retry-queue.d.ts.map +1 -1
- package/dist/retry-queue.js +8 -16
- package/dist/retry-queue.js.map +1 -1
- package/dist/seal-upgrade.d.ts +3 -1
- package/dist/seal-upgrade.d.ts.map +1 -1
- package/dist/seal-upgrade.js +1 -2
- package/dist/seal-upgrade.js.map +1 -1
- package/dist/session-ceremony.d.ts +8 -4
- package/dist/session-ceremony.d.ts.map +1 -1
- package/dist/session-ceremony.js +3 -7
- package/dist/session-ceremony.js.map +1 -1
- package/dist/session-node-manager.d.ts +10 -10
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +45 -45
- package/dist/session-node-manager.js.map +1 -1
- package/dist/sqlcipher-db.d.ts +87 -0
- package/dist/sqlcipher-db.d.ts.map +1 -0
- package/dist/sqlcipher-db.js +259 -0
- package/dist/sqlcipher-db.js.map +1 -0
- package/package.json +5 -4
- package/dist/manifest-version-store-file.d.ts +0 -18
- package/dist/manifest-version-store-file.d.ts.map +0 -1
- package/dist/manifest-version-store-file.js +0 -40
- package/dist/manifest-version-store-file.js.map +0 -1
- package/dist/transcript-cipher.d.ts +0 -31
- package/dist/transcript-cipher.d.ts.map +0 -1
- package/dist/transcript-cipher.js +0 -74
- 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"}
|