@cello-protocol/daemon 0.0.58 → 0.0.60
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-id-migration.d.ts +1 -1
- package/dist/agent-id-migration.d.ts.map +1 -1
- package/dist/agent-id-migration.js +86 -70
- package/dist/agent-id-migration.js.map +1 -1
- package/dist/session-node-manager.d.ts +0 -2
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +56 -163
- package/dist/session-node-manager.js.map +1 -1
- package/dist/sqlcipher-db.d.ts +25 -0
- package/dist/sqlcipher-db.d.ts.map +1 -1
- package/dist/sqlcipher-db.js +73 -0
- package/dist/sqlcipher-db.js.map +1 -1
- package/dist/trust-signal-store.d.ts +164 -0
- package/dist/trust-signal-store.d.ts.map +1 -0
- package/dist/trust-signal-store.js +284 -0
- package/dist/trust-signal-store.js.map +1 -0
- package/package.json +5 -5
package/dist/sqlcipher-db.js
CHANGED
|
@@ -242,8 +242,81 @@ export function openEncryptedDatabase(dbPath, dbKey, logger) {
|
|
|
242
242
|
catch (err) {
|
|
243
243
|
logger?.warn("persist.db.wal.unavailable", { error: err instanceof Error ? err.message : String(err) });
|
|
244
244
|
}
|
|
245
|
+
// M10-D19: FOREIGN KEY enforcement. SQLite defaults this OFF — and with it off, a declared
|
|
246
|
+
// `FOREIGN KEY` is DECORATIVE: SQLite happily accepts an orphan row, so a schema that looks like it
|
|
247
|
+
// enforces a relationship enforces nothing. INV-AGENT-SCOPED depends on this being real (a received
|
|
248
|
+
// trust signal cannot exist except hung off one agent's contact row — `contact_trust_signals` FKs to
|
|
249
|
+
// `contacts(agent_id, pubkey)`), and ON DELETE CASCADE only fires when it is on.
|
|
250
|
+
//
|
|
251
|
+
// It is per-CONNECTION, not per-database, so it belongs here at open — not in a migration, which
|
|
252
|
+
// would set it for exactly one connection and leave every later one unguarded.
|
|
253
|
+
//
|
|
254
|
+
// Safe to enable on existing databases: the daemon declared ZERO foreign keys before this, so there
|
|
255
|
+
// is no pre-existing constraint that enforcement could retroactively violate.
|
|
256
|
+
inner.pragma("foreign_keys = ON");
|
|
257
|
+
const fkOn = inner.pragma("foreign_keys", { simple: true });
|
|
258
|
+
if (fkOn !== 1 && fkOn !== true) {
|
|
259
|
+
// Refuse to run with a silently-unenforced schema. If FKs cannot be turned on, every FK in the
|
|
260
|
+
// DB is a lie and INV-AGENT-SCOPED is unenforced — that is not a degraded mode we announce and
|
|
261
|
+
// continue through, it is a broken security property. ABSENT IS NOT FINE.
|
|
262
|
+
try {
|
|
263
|
+
inner.close();
|
|
264
|
+
}
|
|
265
|
+
catch { /* ignore */ }
|
|
266
|
+
throw new DbEncryptionError("db_open_failed", `PRAGMA foreign_keys did not take effect (reported ${String(fkOn)})`, "This SQLite build will not enforce foreign keys, so per-agent scoping of received trust signals cannot be guaranteed. The daemon will not run with an unenforced schema.");
|
|
267
|
+
}
|
|
245
268
|
return new SqlcipherDatabase(inner);
|
|
246
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Run a table REBUILD (the create-copy-drop-rename recipe) with foreign keys safely disabled.
|
|
272
|
+
*
|
|
273
|
+
* THE TRAP THIS EXISTS TO CLOSE. With `PRAGMA foreign_keys = ON` (M10-D19), SQLite treats
|
|
274
|
+
* `DROP TABLE parent` as an implicit `DELETE FROM parent` — which **fires `ON DELETE CASCADE` and
|
|
275
|
+
* silently empties every child table**. No error. No log. The rebuild's own row-count guards count
|
|
276
|
+
* the table being rebuilt, not its children, so they pass. Measured:
|
|
277
|
+
*
|
|
278
|
+
* children before rebuild: 1
|
|
279
|
+
* PRAGMA foreign_keys = OFF (inside BEGIN) -> still reports 1 <-- A SILENT NO-OP
|
|
280
|
+
* DROP TABLE contacts -> children: 0 <-- cascade fired
|
|
281
|
+
*
|
|
282
|
+
* And the obvious mitigation does not work: **`PRAGMA foreign_keys` is a no-op inside a
|
|
283
|
+
* transaction.** SQLite ignores it and says nothing. Since every rebuild in this codebase runs
|
|
284
|
+
* inside one `BEGIN…COMMIT` (agent-id-migration.ts rebuilds seven tables, `contacts` among them),
|
|
285
|
+
* a rebuild that "disabled" FKs in the usual place would still cascade.
|
|
286
|
+
*
|
|
287
|
+
* So the pragma must be toggled OUTSIDE the transaction, which is what this helper enforces — and it
|
|
288
|
+
* VERIFIES the toggle took effect rather than assuming it, because the failure is silent by nature.
|
|
289
|
+
* On the way out it re-enables FKs and runs `PRAGMA foreign_key_check`: a rebuild that left a
|
|
290
|
+
* dangling reference (e.g. `ALTER TABLE parent RENAME` rewrites children's FK clauses to point at
|
|
291
|
+
* the renamed table) is a loud failure here rather than a mystery on some later insert.
|
|
292
|
+
*/
|
|
293
|
+
export function withForeignKeysOff(db, logger, fn) {
|
|
294
|
+
db.exec("PRAGMA foreign_keys = OFF");
|
|
295
|
+
// Verify. If we are inside a transaction the pragma was silently ignored, and proceeding would
|
|
296
|
+
// cascade-delete children on the first DROP. Refuse — do not rebuild with FKs live.
|
|
297
|
+
const off = db.prepare("PRAGMA foreign_keys").get();
|
|
298
|
+
if (off?.foreign_keys !== 0) {
|
|
299
|
+
throw new Error("table rebuild refused: PRAGMA foreign_keys = OFF did not take effect (still " +
|
|
300
|
+
`${String(off?.foreign_keys)}). PRAGMA foreign_keys is a NO-OP inside a transaction — this ` +
|
|
301
|
+
"helper must be called OUTSIDE any BEGIN. Rebuilding with FKs live makes DROP TABLE <parent> " +
|
|
302
|
+
"cascade-delete every child row, silently.");
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
return fn();
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
309
|
+
const violations = db.prepare("PRAGMA foreign_key_check").all();
|
|
310
|
+
if (violations.length > 0) {
|
|
311
|
+
// The rebuild left dangling references. Loud — an FK that points at a table that no longer
|
|
312
|
+
// exists (or a renamed one) fails on some unrelated insert much later, naming the wrong
|
|
313
|
+
// subsystem entirely.
|
|
314
|
+
logger?.error("persist.db.rebuild.fk_violation", { violations: violations.length });
|
|
315
|
+
throw new Error(`table rebuild left ${violations.length} foreign-key violation(s) — PRAGMA foreign_key_check ` +
|
|
316
|
+
"is non-empty. The rebuilt table's children now reference a parent that is missing or renamed.");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
247
320
|
/**
|
|
248
321
|
* Convenience used by tests and tooling: resolve the key file beside `dbPath` and open. Throws if
|
|
249
322
|
* the key file is absent (it does NOT generate one) — callers inspecting an existing DB always have
|
package/dist/sqlcipher-db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlcipher-db.js","sourceRoot":"","sources":["../src/sqlcipher-db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"sqlcipher-db.js","sourceRoot":"","sources":["../src/sqlcipher-db.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M10 / DOD-STORE-CLIENT-1 — the daemon's two trust-signal tables.
|
|
3
|
+
*
|
|
4
|
+
* TWO tables, never one with a role flag (M10-D4). They answer different questions and obey
|
|
5
|
+
* different scoping rules, and a single table with a `role` column is how one set of rules ends up
|
|
6
|
+
* applied to the other's rows.
|
|
7
|
+
*
|
|
8
|
+
* `wallet_trust_signals` — signals ABOUT this daemon's agents, held so they can be PRESENTED.
|
|
9
|
+
* `contact_trust_signals` — signals OTHER agents PRESENTED TO one of my agents.
|
|
10
|
+
*
|
|
11
|
+
* THE WALLET CARRIES NO AGENT ASSOCIATION (M10-D14). PK = `signal_hash`; one row per signal per
|
|
12
|
+
* daemon, and nothing more. It is the envelope's own HASHED `subject_kind`/`subject` that decides
|
|
13
|
+
* who may present it, evaluated at presentation time — not a local attribution decided at delivery
|
|
14
|
+
* time. That is the whole point: an account-subject signal (phone, email) serves every agent under
|
|
15
|
+
* the account, so **adding an agent to an existing daemon is ZERO signal work** — no assignment
|
|
16
|
+
* sweep, no re-attribution at renewal, no copies to keep in step at expiry. A per-agent column
|
|
17
|
+
* would quietly reintroduce every one of those chores.
|
|
18
|
+
*
|
|
19
|
+
* THE RECEIVED STORE IS PER-AGENT, AND THE DATABASE ENFORCES IT (INV-AGENT-SCOPED). Consent is
|
|
20
|
+
* genuinely per-agent here: a signal Bob showed to my agent `alice` must be invisible to my agent
|
|
21
|
+
* `bob` on the same daemon. `agent_id` is NOT NULL and the row hangs off a contact row by composite
|
|
22
|
+
* FK, so an unscoped row cannot be written at all — as opposed to being merely discouraged by a
|
|
23
|
+
* query convention that every future caller must remember. (This requires `PRAGMA foreign_keys = ON`,
|
|
24
|
+
* set at open in sqlcipher-db.ts — M10-D19. Without it the FK is decorative.) This is where the M8
|
|
25
|
+
* scaffold's `agent_id = null` defect dies.
|
|
26
|
+
*
|
|
27
|
+
* EVIDENCE, NOT AN INPUT (M10-D4 / INV-STATELESS-RECIPIENT). Received rows are re-checkable evidence
|
|
28
|
+
* of what was presented and when we verified it. They are NEVER an input to policy evaluation, which
|
|
29
|
+
* consumes only the currently-presented set, and they are never trusted for freshness (that is
|
|
30
|
+
* re-checked on use). This module therefore exposes no "is this contact trusted?" read — the absence
|
|
31
|
+
* is deliberate and structural. Do not add one.
|
|
32
|
+
*
|
|
33
|
+
* OPAQUE THROUGHOUT (INV-ZERO-BUMP / INV-TYPE-CARRY). `type` is a TEXT column with no CHECK, no
|
|
34
|
+
* enum, and no index predicated on a type value; `payload` is a BLOB that is never parsed and whose
|
|
35
|
+
* fields are never hoisted into columns. A type string this code has never seen stores, reads, and
|
|
36
|
+
* presents exactly like a known one. Adding a signal type must require no change to this file.
|
|
37
|
+
*/
|
|
38
|
+
import type { DaemonDatabase } from "./sqlcipher-db.js";
|
|
39
|
+
import type { Logger } from "./types.js";
|
|
40
|
+
/** Status lives OUTSIDE the hash — it is mutable after minting, which is exactly why it is not in
|
|
41
|
+
* the preimage. If it were hashed, revoking a signal would change its hash and the directory could
|
|
42
|
+
* never find it again. */
|
|
43
|
+
export type SignalStatus = "active" | "revoked" | "superseded";
|
|
44
|
+
/** The envelope, as this store takes and returns it. Mirrors `TrustSignalEnvelope` in
|
|
45
|
+
* protocol-types, plus the two non-hashed local columns. */
|
|
46
|
+
export interface WalletSignalInput {
|
|
47
|
+
signalHash: string;
|
|
48
|
+
subjectKind: "account" | "agent";
|
|
49
|
+
subject: string;
|
|
50
|
+
issuerKind: "portal" | "agent";
|
|
51
|
+
issuerPubkey: string;
|
|
52
|
+
/** OPAQUE. Never gated on, never enumerated. */
|
|
53
|
+
type: string;
|
|
54
|
+
schemaVersion: number;
|
|
55
|
+
/** OPAQUE bytes. Never parsed. */
|
|
56
|
+
payload: Uint8Array;
|
|
57
|
+
issuedAt: number;
|
|
58
|
+
expiresAt: number | null;
|
|
59
|
+
supersedesHash: string | null;
|
|
60
|
+
status: SignalStatus | string;
|
|
61
|
+
}
|
|
62
|
+
export interface WalletSignalRow extends WalletSignalInput {
|
|
63
|
+
receivedAt: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A signal a contact PRESENTED to one of my agents, as it is stored after verification.
|
|
67
|
+
*
|
|
68
|
+
* ⚠️ NOTE WHAT IS ABSENT: there is no `status` field, and this type deliberately does NOT extend
|
|
69
|
+
* `WalletSignalInput`.
|
|
70
|
+
*
|
|
71
|
+
* `status` is outside the hash preimage — that is what makes it mutable, and it also means it is
|
|
72
|
+
* **NOT AUTHENTICATED BY THE SIGNAL HASH**. A presenter can claim any status they like. If this type
|
|
73
|
+
* simply extended the wallet's, the peer's claimed `status` would ride in the same struct as the
|
|
74
|
+
* envelope fields and the natural call site would pass it straight through to the database.
|
|
75
|
+
*
|
|
76
|
+
* The attack that permits: Bob presents signal H; we verify it, find it REVOKED at the directory,
|
|
77
|
+
* and store that as evidence. Next session Bob re-presents the same hash claiming `status: active`.
|
|
78
|
+
* An upsert on the peer's value overwrites the one durable record that says we caught it — the party
|
|
79
|
+
* a revocation indicts gets to erase the indictment. Evidence an adversary can rewrite is not
|
|
80
|
+
* evidence.
|
|
81
|
+
*
|
|
82
|
+
* So the stored status is OUR verification verdict (`verdict`), produced by the verification seam
|
|
83
|
+
* (DOD-VERIFY-1) from what the DIRECTORY said — never a field the peer controls. Removing `status`
|
|
84
|
+
* from the input type makes that structural: there is no pass-through to forget to block.
|
|
85
|
+
*/
|
|
86
|
+
export interface ReceivedSignalInput extends Omit<WalletSignalInput, "status"> {
|
|
87
|
+
agentId: string;
|
|
88
|
+
contactPubkey: string;
|
|
89
|
+
verifiedAt: number;
|
|
90
|
+
/** OUR verdict from re-checking the directory — never the presenter's claim. */
|
|
91
|
+
verdict: SignalStatus;
|
|
92
|
+
}
|
|
93
|
+
export interface ReceivedSignalRow extends ReceivedSignalInput {
|
|
94
|
+
receivedAt: number;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Idempotent schema. Must run AFTER `contacts` exists — the composite FK's parent. SQLite resolves
|
|
98
|
+
* an FK's parent at DML time, not at DDL time, so creating this first would not fail here; it would
|
|
99
|
+
* fail later, on the first insert, which is a far worse place to find out.
|
|
100
|
+
*/
|
|
101
|
+
export declare function ensureTrustSignalSchema(db: DaemonDatabase, _logger: Logger): void;
|
|
102
|
+
export declare class TrustSignalStore {
|
|
103
|
+
#private;
|
|
104
|
+
/**
|
|
105
|
+
* Does NOT create the schema. `SessionNodeManager.initialize()` owns it, and must — the received
|
|
106
|
+
* table's FK parent is `contacts`, which that init creates. A store that created its own schema
|
|
107
|
+
* would happily build `contact_trust_signals` on any handle lacking `contacts`, producing a
|
|
108
|
+
* dangling FK that then fails on the first INSERT with `no such table: main.contacts` — an error
|
|
109
|
+
* that names the wrong subsystem, at the wrong time, far from the cause.
|
|
110
|
+
*/
|
|
111
|
+
constructor(db: DaemonDatabase, logger: Logger);
|
|
112
|
+
/**
|
|
113
|
+
* Store a signal this daemon holds about one of its agents.
|
|
114
|
+
*
|
|
115
|
+
* `INSERT OR IGNORE`, and that is a correctness property, not a convenience: the row is keyed by
|
|
116
|
+
* its own content hash, so a second delivery of the same hash is by definition the same signal.
|
|
117
|
+
* If the bytes really differed, the hash would differ. A differing payload under an identical hash
|
|
118
|
+
* is therefore a liar, and it must not overwrite the truth. This is also what makes duplicate
|
|
119
|
+
* delivery a no-op and wallet rows safely mergeable across a user's daemons (spec §14.11).
|
|
120
|
+
*/
|
|
121
|
+
putWalletSignal(s: WalletSignalInput): void;
|
|
122
|
+
getWalletSignal(signalHash: string): WalletSignalRow | null;
|
|
123
|
+
/**
|
|
124
|
+
* Status is the ONE mutable field — it lives outside the hash precisely so this is possible.
|
|
125
|
+
*
|
|
126
|
+
* REVOCATION IS TERMINAL. A revoked signal can never go back to `active`. Without that guard this
|
|
127
|
+
* is a blind UPDATE, and a stale or replayed directory read reporting `active` for a hash we
|
|
128
|
+
* already revoked would resurrect it — and `listPresentable` would start offering it again. The
|
|
129
|
+
* directory is the authority on revocation, but a LATE answer from it is not a NEW answer; the
|
|
130
|
+
* monotonic rule makes out-of-order responses harmless instead of dangerous.
|
|
131
|
+
*
|
|
132
|
+
* `superseded` may still be overtaken by `revoked` (a superseded signal can also be revoked), but
|
|
133
|
+
* never the reverse, and never back to `active`.
|
|
134
|
+
*/
|
|
135
|
+
setWalletStatus(signalHash: string, status: SignalStatus): void;
|
|
136
|
+
/**
|
|
137
|
+
* The signals `agentId` may present, resolved from the ENVELOPE, not from a stored attribution:
|
|
138
|
+
* an `account`-subject row is presentable by every agent under `accountId`; an `agent`-subject row
|
|
139
|
+
* only by its own subject (M10-D5/M10-D14).
|
|
140
|
+
*
|
|
141
|
+
* Excludes expired and non-active rows. Selective disclosure (all / some / none) is the CALLER's
|
|
142
|
+
* choice on top of this — DOD-PRESENT-1; this returns what is *eligible*, never what to send.
|
|
143
|
+
*/
|
|
144
|
+
listPresentable(opts: {
|
|
145
|
+
agentId: string;
|
|
146
|
+
accountId: string;
|
|
147
|
+
nowSec?: number;
|
|
148
|
+
}): WalletSignalRow[];
|
|
149
|
+
/**
|
|
150
|
+
* Store a signal a contact PRESENTED to one of my agents, after it was verified.
|
|
151
|
+
*
|
|
152
|
+
* The FK refuses a row for a contact that does not exist — an unscoped received signal is not
|
|
153
|
+
* merely discouraged, it is unwritable. `verified_at` is RE-STAMPED on re-presentation: it records
|
|
154
|
+
* when WE last re-verified, and a stale value that looked fresh would be worse than no value at
|
|
155
|
+
* all (freshness is re-checked on use — M10-D4).
|
|
156
|
+
*/
|
|
157
|
+
putReceivedSignal(s: ReceivedSignalInput): void;
|
|
158
|
+
/** Evidence only. Never an input to policy — see the header. */
|
|
159
|
+
listReceived(opts: {
|
|
160
|
+
agentId: string;
|
|
161
|
+
contactPubkey: string;
|
|
162
|
+
}): ReceivedSignalRow[];
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=trust-signal-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-signal-store.d.ts","sourceRoot":"","sources":["../src/trust-signal-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC;;2BAE2B;AAC3B,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC;AAE/D;6DAC6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,SAAS,GAAG,OAAO,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,OAAO,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,UAAU,EAAE,MAAM,CAAC;CACpB;AAmDD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAMjF;AAiED,qBAAa,gBAAgB;;IAI3B;;;;;;OAMG;gBACS,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM;IAK9C;;;;;;;;OAQG;IACH,eAAe,CAAC,CAAC,EAAE,iBAAiB,GAAG,IAAI;IAmB3C,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAO3D;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI;IA0B/D;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,eAAe,EAAE;IAqBjG;;;;;;;OAOG;IACH,iBAAiB,CAAC,CAAC,EAAE,mBAAmB,GAAG,IAAI;IA8B/C,gEAAgE;IAChE,YAAY,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,GAAG,iBAAiB,EAAE;CAiBpF"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M10 / DOD-STORE-CLIENT-1 — the daemon's two trust-signal tables.
|
|
3
|
+
*
|
|
4
|
+
* TWO tables, never one with a role flag (M10-D4). They answer different questions and obey
|
|
5
|
+
* different scoping rules, and a single table with a `role` column is how one set of rules ends up
|
|
6
|
+
* applied to the other's rows.
|
|
7
|
+
*
|
|
8
|
+
* `wallet_trust_signals` — signals ABOUT this daemon's agents, held so they can be PRESENTED.
|
|
9
|
+
* `contact_trust_signals` — signals OTHER agents PRESENTED TO one of my agents.
|
|
10
|
+
*
|
|
11
|
+
* THE WALLET CARRIES NO AGENT ASSOCIATION (M10-D14). PK = `signal_hash`; one row per signal per
|
|
12
|
+
* daemon, and nothing more. It is the envelope's own HASHED `subject_kind`/`subject` that decides
|
|
13
|
+
* who may present it, evaluated at presentation time — not a local attribution decided at delivery
|
|
14
|
+
* time. That is the whole point: an account-subject signal (phone, email) serves every agent under
|
|
15
|
+
* the account, so **adding an agent to an existing daemon is ZERO signal work** — no assignment
|
|
16
|
+
* sweep, no re-attribution at renewal, no copies to keep in step at expiry. A per-agent column
|
|
17
|
+
* would quietly reintroduce every one of those chores.
|
|
18
|
+
*
|
|
19
|
+
* THE RECEIVED STORE IS PER-AGENT, AND THE DATABASE ENFORCES IT (INV-AGENT-SCOPED). Consent is
|
|
20
|
+
* genuinely per-agent here: a signal Bob showed to my agent `alice` must be invisible to my agent
|
|
21
|
+
* `bob` on the same daemon. `agent_id` is NOT NULL and the row hangs off a contact row by composite
|
|
22
|
+
* FK, so an unscoped row cannot be written at all — as opposed to being merely discouraged by a
|
|
23
|
+
* query convention that every future caller must remember. (This requires `PRAGMA foreign_keys = ON`,
|
|
24
|
+
* set at open in sqlcipher-db.ts — M10-D19. Without it the FK is decorative.) This is where the M8
|
|
25
|
+
* scaffold's `agent_id = null` defect dies.
|
|
26
|
+
*
|
|
27
|
+
* EVIDENCE, NOT AN INPUT (M10-D4 / INV-STATELESS-RECIPIENT). Received rows are re-checkable evidence
|
|
28
|
+
* of what was presented and when we verified it. They are NEVER an input to policy evaluation, which
|
|
29
|
+
* consumes only the currently-presented set, and they are never trusted for freshness (that is
|
|
30
|
+
* re-checked on use). This module therefore exposes no "is this contact trusted?" read — the absence
|
|
31
|
+
* is deliberate and structural. Do not add one.
|
|
32
|
+
*
|
|
33
|
+
* OPAQUE THROUGHOUT (INV-ZERO-BUMP / INV-TYPE-CARRY). `type` is a TEXT column with no CHECK, no
|
|
34
|
+
* enum, and no index predicated on a type value; `payload` is a BLOB that is never parsed and whose
|
|
35
|
+
* fields are never hoisted into columns. A type string this code has never seen stores, reads, and
|
|
36
|
+
* presents exactly like a known one. Adding a signal type must require no change to this file.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* ⚠️ TWO TIME UNITS LIVE IN THESE TABLES, AND MIXING THEM SILENTLY PRESENTS EXPIRED SIGNALS.
|
|
40
|
+
*
|
|
41
|
+
* issued_at / expires_at — epoch **SECONDS**. These are ENVELOPE fields: they are HASHED, so the
|
|
42
|
+
* protocol fixed the unit and we do not get to choose it.
|
|
43
|
+
* received_at / verified_at — epoch **MILLISECONDS** (`Date.now()`). Local bookkeeping only, never
|
|
44
|
+
* hashed, and matching the house convention of every other daemon table
|
|
45
|
+
* (`contacts.added_at`, `sessions.updated_at`).
|
|
46
|
+
*
|
|
47
|
+
* A factor-of-1000 error between them does not throw. Compared against a 1970 timestamp, every
|
|
48
|
+
* expiry is still in the future — so the failure mode is an expired signal being cheerfully
|
|
49
|
+
* presented. Anything that compares against `expires_at` must be in SECONDS; see `listPresentable`.
|
|
50
|
+
*/
|
|
51
|
+
const ENVELOPE_COLUMNS = `
|
|
52
|
+
signal_hash TEXT NOT NULL,
|
|
53
|
+
subject_kind TEXT NOT NULL,
|
|
54
|
+
subject TEXT NOT NULL,
|
|
55
|
+
issuer_kind TEXT NOT NULL,
|
|
56
|
+
issuer_pubkey TEXT NOT NULL,
|
|
57
|
+
type TEXT NOT NULL,
|
|
58
|
+
schema_version INTEGER NOT NULL,
|
|
59
|
+
payload BLOB NOT NULL,
|
|
60
|
+
issued_at INTEGER NOT NULL,
|
|
61
|
+
expires_at INTEGER,
|
|
62
|
+
supersedes_hash TEXT,
|
|
63
|
+
status TEXT NOT NULL`;
|
|
64
|
+
/** Content-addressed: the row IS its hash (spec §14.11 — this is what makes wallet rows portable
|
|
65
|
+
* between a user's daemons, and duplicate delivery a no-op). */
|
|
66
|
+
const CREATE_WALLET_SQL = `
|
|
67
|
+
CREATE TABLE IF NOT EXISTS wallet_trust_signals (
|
|
68
|
+
${ENVELOPE_COLUMNS},
|
|
69
|
+
received_at INTEGER NOT NULL,
|
|
70
|
+
PRIMARY KEY (signal_hash)
|
|
71
|
+
);
|
|
72
|
+
`;
|
|
73
|
+
const CREATE_RECEIVED_SQL = `
|
|
74
|
+
CREATE TABLE IF NOT EXISTS contact_trust_signals (
|
|
75
|
+
agent_id TEXT NOT NULL,
|
|
76
|
+
contact_pubkey TEXT NOT NULL,
|
|
77
|
+
${ENVELOPE_COLUMNS},
|
|
78
|
+
verified_at INTEGER NOT NULL,
|
|
79
|
+
received_at INTEGER NOT NULL,
|
|
80
|
+
PRIMARY KEY (agent_id, contact_pubkey, signal_hash),
|
|
81
|
+
FOREIGN KEY (agent_id, contact_pubkey) REFERENCES contacts(agent_id, pubkey) ON DELETE CASCADE
|
|
82
|
+
);
|
|
83
|
+
`;
|
|
84
|
+
/**
|
|
85
|
+
* Idempotent schema. Must run AFTER `contacts` exists — the composite FK's parent. SQLite resolves
|
|
86
|
+
* an FK's parent at DML time, not at DDL time, so creating this first would not fail here; it would
|
|
87
|
+
* fail later, on the first insert, which is a far worse place to find out.
|
|
88
|
+
*/
|
|
89
|
+
export function ensureTrustSignalSchema(db, _logger) {
|
|
90
|
+
db.exec(CREATE_WALLET_SQL);
|
|
91
|
+
db.exec(CREATE_RECEIVED_SQL);
|
|
92
|
+
// Presentation reads by subject; nothing reads by `type`, and nothing may (INV-ZERO-BUMP — an
|
|
93
|
+
// index predicated on a type VALUE is a per-type construct in the schema).
|
|
94
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_wallet_signals_subject ON wallet_trust_signals (subject_kind, subject)");
|
|
95
|
+
}
|
|
96
|
+
const toBuf = (b) => Buffer.from(b);
|
|
97
|
+
/**
|
|
98
|
+
* The payload column is `BLOB NOT NULL`, so an unrecognized shape coming back from the driver means
|
|
99
|
+
* something is wrong — NOT that the payload is empty.
|
|
100
|
+
*
|
|
101
|
+
* Returning `new Uint8Array(0)` here (the previous behavior) is the silent-fallback pattern: a signal
|
|
102
|
+
* whose payload failed to materialise would flow on to presentation and verification looking like a
|
|
103
|
+
* perfectly valid signal that simply has no content. Its hash would then not match, and the operator
|
|
104
|
+
* would be sent to hunt a canonicalization bug that does not exist. ABSENT IS NOT FINE — refuse, and
|
|
105
|
+
* name the actual cause.
|
|
106
|
+
*/
|
|
107
|
+
const toBytes = (v) => {
|
|
108
|
+
if (v instanceof Uint8Array)
|
|
109
|
+
return new Uint8Array(v);
|
|
110
|
+
if (Buffer.isBuffer(v))
|
|
111
|
+
return new Uint8Array(v);
|
|
112
|
+
throw new Error(`signal_payload_not_bytes: the payload column returned ${v === null ? "null" : typeof v}` +
|
|
113
|
+
`${v && typeof v === "object" ? ` (${v.constructor?.name})` : ""}, but the column is BLOB NOT NULL. ` +
|
|
114
|
+
"This is a storage-layer fault, not an empty payload — an empty payload would be a zero-length BLOB.");
|
|
115
|
+
};
|
|
116
|
+
function toWalletRow(r) {
|
|
117
|
+
return {
|
|
118
|
+
signalHash: r.signal_hash,
|
|
119
|
+
subjectKind: r.subject_kind,
|
|
120
|
+
subject: r.subject,
|
|
121
|
+
issuerKind: r.issuer_kind,
|
|
122
|
+
issuerPubkey: r.issuer_pubkey,
|
|
123
|
+
type: r.type,
|
|
124
|
+
schemaVersion: r.schema_version,
|
|
125
|
+
payload: toBytes(r.payload),
|
|
126
|
+
issuedAt: r.issued_at,
|
|
127
|
+
expiresAt: r.expires_at,
|
|
128
|
+
supersedesHash: r.supersedes_hash,
|
|
129
|
+
status: r.status,
|
|
130
|
+
receivedAt: r.received_at,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export class TrustSignalStore {
|
|
134
|
+
#db;
|
|
135
|
+
#logger;
|
|
136
|
+
/**
|
|
137
|
+
* Does NOT create the schema. `SessionNodeManager.initialize()` owns it, and must — the received
|
|
138
|
+
* table's FK parent is `contacts`, which that init creates. A store that created its own schema
|
|
139
|
+
* would happily build `contact_trust_signals` on any handle lacking `contacts`, producing a
|
|
140
|
+
* dangling FK that then fails on the first INSERT with `no such table: main.contacts` — an error
|
|
141
|
+
* that names the wrong subsystem, at the wrong time, far from the cause.
|
|
142
|
+
*/
|
|
143
|
+
constructor(db, logger) {
|
|
144
|
+
this.#db = db;
|
|
145
|
+
this.#logger = logger;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Store a signal this daemon holds about one of its agents.
|
|
149
|
+
*
|
|
150
|
+
* `INSERT OR IGNORE`, and that is a correctness property, not a convenience: the row is keyed by
|
|
151
|
+
* its own content hash, so a second delivery of the same hash is by definition the same signal.
|
|
152
|
+
* If the bytes really differed, the hash would differ. A differing payload under an identical hash
|
|
153
|
+
* is therefore a liar, and it must not overwrite the truth. This is also what makes duplicate
|
|
154
|
+
* delivery a no-op and wallet rows safely mergeable across a user's daemons (spec §14.11).
|
|
155
|
+
*/
|
|
156
|
+
putWalletSignal(s) {
|
|
157
|
+
const res = this.#db
|
|
158
|
+
.prepare(`INSERT OR IGNORE INTO wallet_trust_signals
|
|
159
|
+
(signal_hash, subject_kind, subject, issuer_kind, issuer_pubkey, type, schema_version,
|
|
160
|
+
payload, issued_at, expires_at, supersedes_hash, status, received_at)
|
|
161
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
162
|
+
.run(s.signalHash, s.subjectKind, s.subject, s.issuerKind, s.issuerPubkey, s.type, s.schemaVersion, toBuf(s.payload), s.issuedAt, s.expiresAt, s.supersedesHash, s.status, Date.now());
|
|
163
|
+
if (res.changes > 0) {
|
|
164
|
+
this.#logger.info("signal.wallet.stored", {
|
|
165
|
+
signalHash: s.signalHash, type: s.type, subjectKind: s.subjectKind, issuerKind: s.issuerKind,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
getWalletSignal(signalHash) {
|
|
170
|
+
const row = this.#db
|
|
171
|
+
.prepare("SELECT * FROM wallet_trust_signals WHERE signal_hash = ?")
|
|
172
|
+
.get(signalHash);
|
|
173
|
+
return row ? toWalletRow(row) : null;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Status is the ONE mutable field — it lives outside the hash precisely so this is possible.
|
|
177
|
+
*
|
|
178
|
+
* REVOCATION IS TERMINAL. A revoked signal can never go back to `active`. Without that guard this
|
|
179
|
+
* is a blind UPDATE, and a stale or replayed directory read reporting `active` for a hash we
|
|
180
|
+
* already revoked would resurrect it — and `listPresentable` would start offering it again. The
|
|
181
|
+
* directory is the authority on revocation, but a LATE answer from it is not a NEW answer; the
|
|
182
|
+
* monotonic rule makes out-of-order responses harmless instead of dangerous.
|
|
183
|
+
*
|
|
184
|
+
* `superseded` may still be overtaken by `revoked` (a superseded signal can also be revoked), but
|
|
185
|
+
* never the reverse, and never back to `active`.
|
|
186
|
+
*/
|
|
187
|
+
setWalletStatus(signalHash, status) {
|
|
188
|
+
const res = this.#db
|
|
189
|
+
.prepare(`UPDATE wallet_trust_signals SET status = ?
|
|
190
|
+
WHERE signal_hash = ?
|
|
191
|
+
AND status != 'revoked'
|
|
192
|
+
AND NOT (status = 'superseded' AND ? = 'active')`)
|
|
193
|
+
.run(status, signalHash, status);
|
|
194
|
+
if (res.changes > 0) {
|
|
195
|
+
this.#logger.info("signal.wallet.status.changed", { signalHash, status });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// No change is not necessarily an error (the row may already hold this status), but a REFUSED
|
|
199
|
+
// downgrade is worth surfacing — it means something tried to resurrect a dead signal.
|
|
200
|
+
const current = this.#db
|
|
201
|
+
.prepare("SELECT status FROM wallet_trust_signals WHERE signal_hash = ?")
|
|
202
|
+
.get(signalHash);
|
|
203
|
+
if (current && current.status !== status) {
|
|
204
|
+
this.#logger.warn("signal.wallet.status.change_refused", {
|
|
205
|
+
signalHash, current: current.status, attempted: status,
|
|
206
|
+
reason: "revocation is terminal — a revoked or superseded signal is never returned to active",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The signals `agentId` may present, resolved from the ENVELOPE, not from a stored attribution:
|
|
212
|
+
* an `account`-subject row is presentable by every agent under `accountId`; an `agent`-subject row
|
|
213
|
+
* only by its own subject (M10-D5/M10-D14).
|
|
214
|
+
*
|
|
215
|
+
* Excludes expired and non-active rows. Selective disclosure (all / some / none) is the CALLER's
|
|
216
|
+
* choice on top of this — DOD-PRESENT-1; this returns what is *eligible*, never what to send.
|
|
217
|
+
*/
|
|
218
|
+
listPresentable(opts) {
|
|
219
|
+
// `nowSec` is epoch SECONDS, and it is named for its unit deliberately. The envelope's
|
|
220
|
+
// `issued_at`/`expires_at` are seconds (they are HASHED, and the protocol fixed the unit);
|
|
221
|
+
// `Date.now()` is milliseconds. An unnamed `now` here invites a caller to pass one where the
|
|
222
|
+
// other is meant, and a factor-of-1000 error in an expiry check does not throw — it silently
|
|
223
|
+
// presents an EXPIRED signal (compared against a 1970 timestamp, every expiry is still in the
|
|
224
|
+
// future). The first version of this method took `now` and its own test passed seconds into a
|
|
225
|
+
// parameter that was divided by 1000; it only went green because the fixture's expiry was 1.
|
|
226
|
+
const nowSec = opts.nowSec ?? Math.floor(Date.now() / 1000);
|
|
227
|
+
const rows = this.#db
|
|
228
|
+
.prepare(`SELECT * FROM wallet_trust_signals
|
|
229
|
+
WHERE status = 'active'
|
|
230
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
231
|
+
AND ( (subject_kind = 'account' AND subject = ?)
|
|
232
|
+
OR (subject_kind = 'agent' AND subject = ?) )`)
|
|
233
|
+
.all(nowSec, opts.accountId, opts.agentId);
|
|
234
|
+
return rows.map(toWalletRow);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Store a signal a contact PRESENTED to one of my agents, after it was verified.
|
|
238
|
+
*
|
|
239
|
+
* The FK refuses a row for a contact that does not exist — an unscoped received signal is not
|
|
240
|
+
* merely discouraged, it is unwritable. `verified_at` is RE-STAMPED on re-presentation: it records
|
|
241
|
+
* when WE last re-verified, and a stale value that looked fresh would be worse than no value at
|
|
242
|
+
* all (freshness is re-checked on use — M10-D4).
|
|
243
|
+
*/
|
|
244
|
+
putReceivedSignal(s) {
|
|
245
|
+
this.#db
|
|
246
|
+
.prepare(`INSERT INTO contact_trust_signals
|
|
247
|
+
(agent_id, contact_pubkey, signal_hash, subject_kind, subject, issuer_kind, issuer_pubkey,
|
|
248
|
+
type, schema_version, payload, issued_at, expires_at, supersedes_hash, status,
|
|
249
|
+
verified_at, received_at)
|
|
250
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
251
|
+
ON CONFLICT (agent_id, contact_pubkey, signal_hash)
|
|
252
|
+
DO UPDATE SET
|
|
253
|
+
verified_at = excluded.verified_at,
|
|
254
|
+
-- OUR verdict may only ever get WORSE, never better. A re-presentation cannot launder a
|
|
255
|
+
-- signal we already caught as revoked/superseded back to active: the party a revocation
|
|
256
|
+
-- indicts does not get to erase the indictment. (The envelope fields are not updated at
|
|
257
|
+
-- all — they are hashed, so an identical signal_hash means identical envelope bytes; a
|
|
258
|
+
-- differing value under the same hash is a liar and must not win.)
|
|
259
|
+
status = CASE WHEN contact_trust_signals.status = 'active' THEN excluded.status
|
|
260
|
+
ELSE contact_trust_signals.status END`)
|
|
261
|
+
.run(s.agentId, s.contactPubkey, s.signalHash, s.subjectKind, s.subject, s.issuerKind, s.issuerPubkey, s.type, s.schemaVersion, toBuf(s.payload), s.issuedAt, s.expiresAt, s.supersedesHash, s.verdict, s.verifiedAt, Date.now());
|
|
262
|
+
this.#logger.info("signal.received.stored", {
|
|
263
|
+
agentId: s.agentId, signalHash: s.signalHash, type: s.type, issuerKind: s.issuerKind,
|
|
264
|
+
verdict: s.verdict,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
/** Evidence only. Never an input to policy — see the header. */
|
|
268
|
+
listReceived(opts) {
|
|
269
|
+
const rows = this.#db
|
|
270
|
+
.prepare(`SELECT * FROM contact_trust_signals WHERE agent_id = ? AND contact_pubkey = ?`)
|
|
271
|
+
.all(opts.agentId, opts.contactPubkey);
|
|
272
|
+
return rows.map((r) => {
|
|
273
|
+
const { status, ...envelope } = toWalletRow(r);
|
|
274
|
+
return {
|
|
275
|
+
...envelope,
|
|
276
|
+
agentId: opts.agentId,
|
|
277
|
+
contactPubkey: opts.contactPubkey,
|
|
278
|
+
verifiedAt: r.verified_at,
|
|
279
|
+
verdict: status,
|
|
280
|
+
};
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=trust-signal-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-signal-store.js","sourceRoot":"","sources":["../src/trust-signal-store.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.60",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -30,10 +30,10 @@
|
|
|
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/crypto": "0.0.
|
|
34
|
-
"@cello-protocol/gateway": "0.0.
|
|
35
|
-
"@cello-protocol/protocol-types": "0.0.
|
|
36
|
-
"@cello-protocol/transport": "0.0.
|
|
33
|
+
"@cello-protocol/crypto": "0.0.22",
|
|
34
|
+
"@cello-protocol/gateway": "0.0.4",
|
|
35
|
+
"@cello-protocol/protocol-types": "0.0.23",
|
|
36
|
+
"@cello-protocol/transport": "0.0.23"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^25.6.2",
|