@codemem/core 0.27.0 → 0.28.1
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/coordinator-actions.d.ts +7 -0
- package/dist/coordinator-actions.d.ts.map +1 -1
- package/dist/{coordinator-api-YsLNyP4R.js → coordinator-api-Dbgsu1O8.js} +21 -4
- package/dist/{coordinator-api-YsLNyP4R.js.map → coordinator-api-Dbgsu1O8.js.map} +1 -1
- package/dist/coordinator-api.d.ts.map +1 -1
- package/dist/coordinator-runtime.d.ts +1 -0
- package/dist/coordinator-runtime.d.ts.map +1 -1
- package/dist/dedup-key-backfill.d.ts.map +1 -1
- package/dist/extraction-tier-routing.d.ts +16 -0
- package/dist/extraction-tier-routing.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5705 -5066
- package/dist/index.js.map +1 -1
- package/dist/ingest-pipeline.d.ts +26 -0
- package/dist/ingest-pipeline.d.ts.map +1 -1
- package/dist/internal/cloudflare-coordinator.js +2 -2
- package/dist/internal/cloudflare-coordinator.js.map +1 -1
- package/dist/maintenance/ai-structured.d.ts +32 -0
- package/dist/maintenance/ai-structured.d.ts.map +1 -0
- package/dist/maintenance/backfill-narrative.d.ts +18 -0
- package/dist/maintenance/backfill-narrative.d.ts.map +1 -0
- package/dist/maintenance/backfill-tags.d.ts +19 -0
- package/dist/maintenance/backfill-tags.d.ts.map +1 -0
- package/dist/maintenance/dedup-keys.d.ts +32 -0
- package/dist/maintenance/dedup-keys.d.ts.map +1 -0
- package/dist/maintenance/dedup.d.ts +25 -0
- package/dist/maintenance/dedup.d.ts.map +1 -0
- package/dist/maintenance/init-vacuum.d.ts +9 -0
- package/dist/maintenance/init-vacuum.d.ts.map +1 -0
- package/dist/maintenance/low-signal.d.ts +19 -0
- package/dist/maintenance/low-signal.d.ts.map +1 -0
- package/dist/maintenance/memory-role-helpers.d.ts +18 -0
- package/dist/maintenance/memory-role-helpers.d.ts.map +1 -0
- package/dist/maintenance/memory-role-report.d.ts +4 -0
- package/dist/maintenance/memory-role-report.d.ts.map +1 -0
- package/dist/maintenance/narrative-extract.d.ts +2 -0
- package/dist/maintenance/narrative-extract.d.ts.map +1 -0
- package/dist/maintenance/reliability.d.ts +33 -0
- package/dist/maintenance/reliability.d.ts.map +1 -0
- package/dist/maintenance/relink.d.ts +7 -0
- package/dist/maintenance/relink.d.ts.map +1 -0
- package/dist/maintenance/status.d.ts +6 -0
- package/dist/maintenance/status.d.ts.map +1 -0
- package/dist/maintenance/tag-helpers.d.ts +11 -0
- package/dist/maintenance/tag-helpers.d.ts.map +1 -0
- package/dist/maintenance/types.d.ts +246 -0
- package/dist/maintenance/types.d.ts.map +1 -0
- package/dist/maintenance/with-db.d.ts +3 -0
- package/dist/maintenance/with-db.d.ts.map +1 -0
- package/dist/maintenance.d.ts +20 -434
- package/dist/maintenance.d.ts.map +1 -1
- package/dist/observer-client.d.ts +22 -2
- package/dist/observer-client.d.ts.map +1 -1
- package/dist/pack-eval-fixtures.d.ts +2 -0
- package/dist/pack-eval-fixtures.d.ts.map +1 -1
- package/dist/pack.d.ts.map +1 -1
- package/dist/search.d.ts.map +1 -1
- package/dist/summary-dedup-backfill.d.ts +45 -0
- package/dist/summary-dedup-backfill.d.ts.map +1 -0
- package/dist/sync-daemon.d.ts.map +1 -1
- package/dist/sync-pass.d.ts.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -18,9 +18,35 @@
|
|
|
18
18
|
* 11. Persist session summary
|
|
19
19
|
* 12. End session
|
|
20
20
|
*/
|
|
21
|
+
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
21
22
|
import type { IngestPayload } from "./ingest-types.js";
|
|
22
23
|
import { type ObserverClient } from "./observer-client.js";
|
|
23
24
|
import type { MemoryStore } from "./store.js";
|
|
25
|
+
/**
|
|
26
|
+
* Soft-delete prior active `session_summary` memories written by the observer
|
|
27
|
+
* for a session so each session holds at most one live observer summary.
|
|
28
|
+
*
|
|
29
|
+
* The observer pipeline writes a summary per flush batch. Long-running
|
|
30
|
+
* "durable" sessions therefore accumulate hundreds of per-flush summaries that
|
|
31
|
+
* crowd out typed observations and poison retrieval. This helper enforces the
|
|
32
|
+
* "one active observer summary per session" invariant by superseding earlier
|
|
33
|
+
* active rows whose `metadata.source === "observer_summary"`, leaving them
|
|
34
|
+
* soft-deleted for audit (with `superseded_at` metadata) and recording
|
|
35
|
+
* replication ops so peers drop the stale rows.
|
|
36
|
+
*
|
|
37
|
+
* Must be called *before* the new summary is persisted. If it ran afterward,
|
|
38
|
+
* `store.remember`'s title dedupe could return an existing stale summary's id
|
|
39
|
+
* rather than inserting a new row, and this helper would then treat that
|
|
40
|
+
* stale id as the replacement and wipe out newer summaries in the session.
|
|
41
|
+
* Running first ensures the dedupe query sees no prior active matches.
|
|
42
|
+
*
|
|
43
|
+
* Only `observer_summary` rows are touched; manually-created or
|
|
44
|
+
* legacy-imported session summaries are left alone.
|
|
45
|
+
*
|
|
46
|
+
* Returns the ids of the superseded rows so callers can backfill
|
|
47
|
+
* `superseded_by` once the new replacement id is known.
|
|
48
|
+
*/
|
|
49
|
+
export declare function supersedePriorObserverSummaries(store: MemoryStore, d: ReturnType<typeof drizzle>, sessionId: number): number[];
|
|
24
50
|
export interface IngestOptions {
|
|
25
51
|
/** Observer LLM client. */
|
|
26
52
|
observer: ObserverClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingest-pipeline.d.ts","sourceRoot":"","sources":["../src/ingest-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;
|
|
1
|
+
{"version":3,"file":"ingest-pipeline.d.ts","sourceRoot":"","sources":["../src/ingest-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AA0BrD,OAAO,KAAK,EACX,aAAa,EAMb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,KAAK,cAAc,EAAwC,MAAM,sBAAsB,CAAC;AAIjG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAyC9C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,+BAA+B,CAC9C,KAAK,EAAE,WAAW,EAClB,CAAC,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,EAC7B,SAAS,EAAE,MAAM,GACf,MAAM,EAAE,CAgDV;AAuFD,MAAM,WAAW,aAAa;IAC7B,2BAA2B;IAC3B,QAAQ,EAAE,cAAc,CAAC;IACzB,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,KAAK,cAAc,CAAC;IACxF,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gDAAgD;IAChD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yDAAyD;IACzD,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAwCD;;;;;;GAMG;AACH,wBAAsB,MAAM,CAC3B,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,aAAa,GACpB,OAAO,CAAC,IAAI,CAAC,CAmgBf;AA2BD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,SAAK,GAAG,MAAM,CAShF;AAMD;;;;;GAKG;AACH,wBAAsB,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBtF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as e, t } from "../coordinator-api-
|
|
1
|
+
import { r as e, t } from "../coordinator-api-Dbgsu1O8.js";
|
|
2
2
|
//#region src/d1-coordinator-store.ts
|
|
3
3
|
function n(e) {
|
|
4
4
|
if (e == null) throw Error("expected row");
|
|
@@ -131,7 +131,7 @@ var h = class {
|
|
|
131
131
|
return await m(this.db.prepare("UPDATE enrolled_devices SET display_name = ?\n WHERE group_id = ? AND device_id = ?").bind(n, e, t)) > 0;
|
|
132
132
|
}
|
|
133
133
|
async setDeviceEnabled(e, t, n) {
|
|
134
|
-
return await m(this.db.prepare("UPDATE enrolled_devices SET enabled = ?\n WHERE group_id = ? AND device_id = ?").bind(n
|
|
134
|
+
return await m(this.db.prepare("UPDATE enrolled_devices SET enabled = ?\n WHERE group_id = ? AND device_id = ?").bind(+!!n, e, t)) > 0;
|
|
135
135
|
}
|
|
136
136
|
async removeDevice(e, t) {
|
|
137
137
|
return await this.db.prepare("DELETE FROM presence_records WHERE group_id = ? AND device_id = ?").bind(e, t).run(), await this.db.prepare("DELETE FROM coordinator_reciprocal_approvals WHERE group_id = ? AND (requesting_device_id = ? OR requested_device_id = ?)").bind(e, t, t).run(), await m(this.db.prepare("DELETE FROM enrolled_devices WHERE group_id = ? AND device_id = ?").bind(e, t)) > 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare-coordinator.js","names":[],"sources":["../../src/d1-coordinator-store.ts","../../src/d1-coordinator-runtime.ts"],"sourcesContent":["import type {\n\tCoordinatorBootstrapGrant,\n\tCoordinatorCreateBootstrapGrantInput,\n\tCoordinatorCreateInviteInput,\n\tCoordinatorCreateJoinRequestInput,\n\tCoordinatorCreateReciprocalApprovalInput,\n\tCoordinatorEnrollDeviceInput,\n\tCoordinatorEnrollment,\n\tCoordinatorGroup,\n\tCoordinatorInvite,\n\tCoordinatorJoinRequest,\n\tCoordinatorJoinRequestReviewResult,\n\tCoordinatorListReciprocalApprovalsInput,\n\tCoordinatorPeerRecord,\n\tCoordinatorPresenceRecord,\n\tCoordinatorReciprocalApproval,\n\tCoordinatorReviewJoinRequestBootstrapGrantInput,\n\tCoordinatorReviewJoinRequestInput,\n\tCoordinatorStore,\n\tCoordinatorUpsertPresenceInput,\n} from \"./coordinator-store-contract.js\";\n\ninterface D1RunResultLike {\n\tmeta?: {\n\t\tchanges?: number;\n\t};\n}\n\n/**\n * Experimental D1 adapter scaffold.\n *\n * This is intentionally internal for now while the sync-only store contract\n * is still being validated against an async backend shape.\n */\n\nexport interface D1PreparedStatementLike {\n\tbind(...values: unknown[]): D1PreparedStatementLike;\n\tfirst<T = unknown>(): Promise<T | null>;\n\trun(): Promise<unknown>;\n\tall<T = unknown>(): Promise<{ results?: T[] }>;\n\traw<T = unknown>(): Promise<T[]>;\n}\n\nexport interface D1DatabaseLike {\n\tprepare(query: string): D1PreparedStatementLike;\n\tbatch?(statements: D1PreparedStatementLike[]): Promise<unknown[]>;\n\texec?(query: string): Promise<unknown>;\n}\n\nfunction rowToRecord<T>(row: unknown): T {\n\tif (row == null) throw new Error(\"expected row\");\n\treturn row as T;\n}\n\nfunction normalizeAddress(address: string): string {\n\tconst value = address.trim();\n\tif (!value) return \"\";\n\tconst withScheme = value.includes(\"://\") ? value : `http://${value}`;\n\ttry {\n\t\tconst url = new URL(withScheme);\n\t\tif (!url.hostname) return \"\";\n\t\tif (url.port && (Number(url.port) <= 0 || Number(url.port) > 65535)) return \"\";\n\t\treturn url.origin + url.pathname.replace(/\\/+$/, \"\");\n\t} catch {\n\t\treturn \"\";\n\t}\n}\n\nfunction addressDedupeKey(address: string): string {\n\tif (!address) return \"\";\n\ttry {\n\t\tconst parsed = new URL(address);\n\t\tconst host = parsed.hostname.toLowerCase();\n\t\tif (\n\t\t\t(parsed.protocol === \"http:\" || parsed.protocol === \"\") &&\n\t\t\thost &&\n\t\t\tparsed.port &&\n\t\t\tparsed.pathname === \"/\"\n\t\t) {\n\t\t\treturn `${host}:${parsed.port}`;\n\t\t}\n\t} catch {}\n\treturn address;\n}\n\nfunction mergeAddresses(existing: string[], candidates: string[]): string[] {\n\tconst normalized: string[] = [];\n\tconst seen = new Set<string>();\n\tfor (const address of [...existing, ...candidates]) {\n\t\tconst cleaned = normalizeAddress(address);\n\t\tconst key = addressDedupeKey(cleaned);\n\t\tif (!cleaned || seen.has(key)) continue;\n\t\tseen.add(key);\n\t\tnormalized.push(cleaned);\n\t}\n\treturn normalized;\n}\n\nfunction nowISO(): string {\n\treturn new Date().toISOString();\n}\n\nfunction reciprocalPendingPair(\n\trequestingDeviceId: string,\n\trequestedDeviceId: string,\n): {\n\tlow: string;\n\thigh: string;\n} {\n\treturn requestingDeviceId <= requestedDeviceId\n\t\t? { low: requestingDeviceId, high: requestedDeviceId }\n\t\t: { low: requestedDeviceId, high: requestingDeviceId };\n}\n\nfunction isUniqueConstraintError(error: unknown): boolean {\n\treturn error instanceof Error && /unique|constraint/i.test(error.message);\n}\n\nfunction tokenUrlSafe(bytes: number): string {\n\tconst alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n\tconst random = new Uint8Array(bytes);\n\tglobalThis.crypto.getRandomValues(random);\n\tconst output: string[] = [];\n\tfor (const byte of random) {\n\t\toutput.push(alphabet[byte % alphabet.length] ?? \"A\");\n\t}\n\treturn output.join(\"\");\n}\n\nfunction requireTrimmedBootstrapGrantInput(opts: CoordinatorCreateBootstrapGrantInput): {\n\tgroupId: string;\n\tseedDeviceId: string;\n\tworkerDeviceId: string;\n\texpiresAt: string;\n\tcreatedBy: string | null;\n} {\n\tconst groupId = String(opts.groupId ?? \"\").trim();\n\tconst seedDeviceId = String(opts.seedDeviceId ?? \"\").trim();\n\tconst workerDeviceId = String(opts.workerDeviceId ?? \"\").trim();\n\tconst expiresAt = String(opts.expiresAt ?? \"\").trim();\n\tconst createdBy = String(opts.createdBy ?? \"\").trim() || null;\n\tif (!groupId || !seedDeviceId || !workerDeviceId || !expiresAt) {\n\t\tthrow new Error(\"groupId, seedDeviceId, workerDeviceId, and expiresAt are required.\");\n\t}\n\treturn { groupId, seedDeviceId, workerDeviceId, expiresAt, createdBy };\n}\n\nfunction normalizeBootstrapGrantRequest(\n\tinput: CoordinatorReviewJoinRequestBootstrapGrantInput | null | undefined,\n): CoordinatorCreateBootstrapGrantInput | null {\n\tif (!input) return null;\n\tconst seedDeviceId = String(input.seedDeviceId ?? \"\").trim();\n\tconst expiresAt = String(input.expiresAt ?? \"\").trim();\n\tconst createdBy = String(input.createdBy ?? \"\").trim() || null;\n\tif (!seedDeviceId || !expiresAt) {\n\t\tthrow new Error(\"bootstrapGrant.seedDeviceId and expiresAt are required.\");\n\t}\n\treturn {\n\t\tgroupId: \"\",\n\t\tseedDeviceId,\n\t\tworkerDeviceId: \"\",\n\t\texpiresAt,\n\t\tcreatedBy,\n\t};\n}\n\nasync function allRows<T>(statement: D1PreparedStatementLike): Promise<T[]> {\n\tconst result = await statement.all<T>();\n\treturn Array.isArray(result?.results) ? result.results : [];\n}\n\nasync function firstRow<T>(statement: D1PreparedStatementLike): Promise<T | null> {\n\treturn await statement.first<T>();\n}\n\nasync function runChanges(statement: D1PreparedStatementLike): Promise<number> {\n\tconst result = (await statement.run()) as D1RunResultLike | undefined;\n\treturn Number(result?.meta?.changes ?? 0);\n}\n\nexport class D1CoordinatorStore implements CoordinatorStore {\n\treadonly db: D1DatabaseLike;\n\n\tconstructor(db: D1DatabaseLike) {\n\t\tthis.db = db;\n\t}\n\n\tasync close(): Promise<void> {\n\t\t// No-op for D1 bindings.\n\t}\n\n\tasync createGroup(_groupId: string, _displayName?: string | null): Promise<void> {\n\t\tawait this.db\n\t\t\t.prepare(\n\t\t\t\t\"INSERT OR IGNORE INTO groups(group_id, display_name, archived_at, created_at) VALUES (?, ?, NULL, ?)\",\n\t\t\t)\n\t\t\t.bind(_groupId, _displayName ?? null, nowISO())\n\t\t\t.run();\n\t}\n\n\tasync getGroup(_groupId: string): Promise<CoordinatorGroup | null> {\n\t\tconst row = await firstRow<CoordinatorGroup>(\n\t\t\tthis.db\n\t\t\t\t.prepare(\n\t\t\t\t\t\"SELECT group_id, display_name, archived_at, created_at FROM groups WHERE group_id = ?\",\n\t\t\t\t)\n\t\t\t\t.bind(_groupId),\n\t\t);\n\t\treturn row ? rowToRecord<CoordinatorGroup>(row) : null;\n\t}\n\n\tasync listGroups(_includeArchived = false): Promise<CoordinatorGroup[]> {\n\t\tconst where = _includeArchived ? \"\" : \"WHERE archived_at IS NULL\";\n\t\treturn (\n\t\t\tawait allRows<CoordinatorGroup>(\n\t\t\t\tthis.db.prepare(\n\t\t\t\t\t`SELECT group_id, display_name, archived_at, created_at FROM groups ${where} ORDER BY created_at ASC`,\n\t\t\t\t),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorGroup>(row));\n\t}\n\n\tasync renameGroup(_groupId: string, _displayName: string): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\"UPDATE groups SET display_name = ? WHERE group_id = ?\")\n\t\t\t\t\t.bind(_displayName, _groupId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync archiveGroup(_groupId: string, _archivedAt = nowISO()): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\"UPDATE groups SET archived_at = ? WHERE group_id = ? AND archived_at IS NULL\")\n\t\t\t\t\t.bind(_archivedAt, _groupId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync unarchiveGroup(_groupId: string): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t\"UPDATE groups SET archived_at = NULL WHERE group_id = ? AND archived_at IS NOT NULL\",\n\t\t\t\t\t)\n\t\t\t\t\t.bind(_groupId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync enrollDevice(_groupId: string, _opts: CoordinatorEnrollDeviceInput): Promise<void> {\n\t\tawait this.db\n\t\t\t.prepare(`INSERT INTO enrolled_devices(\n\t\t\t\tgroup_id, device_id, public_key, fingerprint, display_name, enabled, created_at\n\t\t\t) VALUES (?, ?, ?, ?, ?, 1, ?)\n\t\t\tON CONFLICT(group_id, device_id) DO UPDATE SET\n\t\t\t\tpublic_key = excluded.public_key,\n\t\t\t\tfingerprint = excluded.fingerprint,\n\t\t\t\tdisplay_name = excluded.display_name,\n\t\t\t\tenabled = 1`)\n\t\t\t.bind(\n\t\t\t\t_groupId,\n\t\t\t\t_opts.deviceId,\n\t\t\t\t_opts.publicKey,\n\t\t\t\t_opts.fingerprint,\n\t\t\t\t_opts.displayName ?? null,\n\t\t\t\tnowISO(),\n\t\t\t)\n\t\t\t.run();\n\t}\n\n\tasync listEnrolledDevices(\n\t\t_groupId: string,\n\t\t_includeDisabled?: boolean,\n\t): Promise<CoordinatorEnrollment[]> {\n\t\tconst where = _includeDisabled ? \"\" : \"AND enabled = 1\";\n\t\treturn (\n\t\t\tawait allRows<CoordinatorEnrollment>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT group_id, device_id, public_key, fingerprint, display_name, enabled, created_at\n\t\t\t\t\t\t FROM enrolled_devices\n\t\t\t\t\t\t WHERE group_id = ? ${where}\n\t\t\t\t\t\t ORDER BY created_at ASC, device_id ASC`)\n\t\t\t\t\t.bind(_groupId),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorEnrollment>(row));\n\t}\n\n\tasync getEnrollment(_groupId: string, _deviceId: string): Promise<CoordinatorEnrollment | null> {\n\t\tconst row = await firstRow<CoordinatorEnrollment>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT group_id, device_id, public_key, fingerprint, display_name, enabled, created_at\n\t\t\t\t\t FROM enrolled_devices\n\t\t\t\t\t WHERE group_id = ? AND device_id = ? AND enabled = 1`)\n\t\t\t\t.bind(_groupId, _deviceId),\n\t\t);\n\t\treturn row ? rowToRecord<CoordinatorEnrollment>(row) : null;\n\t}\n\n\tasync renameDevice(_groupId: string, _deviceId: string, _displayName: string): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`UPDATE enrolled_devices SET display_name = ?\n\t\t\t\t\t WHERE group_id = ? AND device_id = ?`)\n\t\t\t\t\t.bind(_displayName, _groupId, _deviceId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync setDeviceEnabled(_groupId: string, _deviceId: string, _enabled: boolean): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`UPDATE enrolled_devices SET enabled = ?\n\t\t\t\t\t WHERE group_id = ? AND device_id = ?`)\n\t\t\t\t\t.bind(_enabled ? 1 : 0, _groupId, _deviceId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync removeDevice(_groupId: string, _deviceId: string): Promise<boolean> {\n\t\tawait this.db\n\t\t\t.prepare(\"DELETE FROM presence_records WHERE group_id = ? AND device_id = ?\")\n\t\t\t.bind(_groupId, _deviceId)\n\t\t\t.run();\n\t\tawait this.db\n\t\t\t.prepare(\n\t\t\t\t\"DELETE FROM coordinator_reciprocal_approvals WHERE group_id = ? AND (requesting_device_id = ? OR requested_device_id = ?)\",\n\t\t\t)\n\t\t\t.bind(_groupId, _deviceId, _deviceId)\n\t\t\t.run();\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\"DELETE FROM enrolled_devices WHERE group_id = ? AND device_id = ?\")\n\t\t\t\t\t.bind(_groupId, _deviceId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync recordNonce(_deviceId: string, _nonce: string, _createdAt: string): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t\"INSERT OR IGNORE INTO request_nonces(device_id, nonce, created_at) VALUES (?, ?, ?)\",\n\t\t\t\t\t)\n\t\t\t\t\t.bind(_deviceId, _nonce, _createdAt),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync cleanupNonces(_cutoff: string): Promise<void> {\n\t\tawait this.db.prepare(\"DELETE FROM request_nonces WHERE created_at < ?\").bind(_cutoff).run();\n\t}\n\n\tasync createInvite(_opts: CoordinatorCreateInviteInput): Promise<CoordinatorInvite> {\n\t\tconst now = nowISO();\n\t\tconst inviteId = tokenUrlSafe(12);\n\t\tconst token = tokenUrlSafe(24);\n\t\tconst group = await this.getGroup(_opts.groupId);\n\t\tawait this.db\n\t\t\t.prepare(`INSERT INTO coordinator_invites(\n\t\t\t\tinvite_id, group_id, token, policy, expires_at, created_at, created_by, team_name_snapshot, revoked_at\n\t\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)`)\n\t\t\t.bind(\n\t\t\t\tinviteId,\n\t\t\t\t_opts.groupId,\n\t\t\t\ttoken,\n\t\t\t\t_opts.policy,\n\t\t\t\t_opts.expiresAt,\n\t\t\t\tnow,\n\t\t\t\t_opts.createdBy ?? null,\n\t\t\t\tgroup?.display_name ?? null,\n\t\t\t)\n\t\t\t.run();\n\t\tconst row = await firstRow<CoordinatorInvite>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT invite_id, group_id, token, policy, expires_at, created_at, created_by, team_name_snapshot, revoked_at\n\t\t\t\t\t FROM coordinator_invites WHERE invite_id = ?`)\n\t\t\t\t.bind(inviteId),\n\t\t);\n\t\treturn rowToRecord<CoordinatorInvite>(row);\n\t}\n\n\tasync getInviteByToken(_token: string): Promise<CoordinatorInvite | null> {\n\t\tconst row = await firstRow<CoordinatorInvite>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT invite_id, group_id, token, policy, expires_at, created_at, created_by, team_name_snapshot, revoked_at\n\t\t\t\t\t FROM coordinator_invites\n\t\t\t\t\t WHERE token = ?\n\t\t\t\t\t AND revoked_at IS NULL\n\t\t\t\t\t AND expires_at > ?`)\n\t\t\t\t.bind(_token, nowISO()),\n\t\t);\n\t\treturn row ? rowToRecord<CoordinatorInvite>(row) : null;\n\t}\n\n\tasync listInvites(_groupId: string): Promise<CoordinatorInvite[]> {\n\t\treturn (\n\t\t\tawait allRows<CoordinatorInvite>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT invite_id, group_id, token, policy, expires_at, created_at, created_by, team_name_snapshot, revoked_at\n\t\t\t\t\t FROM coordinator_invites WHERE group_id = ?\n\t\t\t\t\t ORDER BY created_at DESC`)\n\t\t\t\t\t.bind(_groupId),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorInvite>(row));\n\t}\n\n\tasync createJoinRequest(\n\t\t_opts: CoordinatorCreateJoinRequestInput,\n\t): Promise<CoordinatorJoinRequest> {\n\t\tconst now = nowISO();\n\t\tconst requestId = tokenUrlSafe(12);\n\t\tawait this.db\n\t\t\t.prepare(`INSERT INTO coordinator_join_requests(\n\t\t\t\trequest_id, group_id, device_id, public_key, fingerprint, display_name, token, status, created_at, reviewed_at, reviewed_by\n\t\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, NULL, NULL)`)\n\t\t\t.bind(\n\t\t\t\trequestId,\n\t\t\t\t_opts.groupId,\n\t\t\t\t_opts.deviceId,\n\t\t\t\t_opts.publicKey,\n\t\t\t\t_opts.fingerprint,\n\t\t\t\t_opts.displayName ?? null,\n\t\t\t\t_opts.token,\n\t\t\t\tnow,\n\t\t\t)\n\t\t\t.run();\n\t\tconst row = await firstRow<CoordinatorJoinRequest>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, device_id, public_key, fingerprint, display_name, token, status, created_at, reviewed_at, reviewed_by\n\t\t\t\t\t FROM coordinator_join_requests WHERE request_id = ?`)\n\t\t\t\t.bind(requestId),\n\t\t);\n\t\treturn rowToRecord<CoordinatorJoinRequest>(row);\n\t}\n\n\tasync listJoinRequests(_groupId: string, _status?: string): Promise<CoordinatorJoinRequest[]> {\n\t\tconst status = _status ?? \"pending\";\n\t\treturn (\n\t\t\tawait allRows<CoordinatorJoinRequest>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT request_id, group_id, device_id, public_key, fingerprint, display_name, token, status, created_at, reviewed_at, reviewed_by\n\t\t\t\t\t\t FROM coordinator_join_requests\n\t\t\t\t\t\t WHERE group_id = ? AND status = ?\n\t\t\t\t\t\t ORDER BY created_at ASC, device_id ASC`)\n\t\t\t\t\t.bind(_groupId, status),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorJoinRequest>(row));\n\t}\n\n\tasync reviewJoinRequest(\n\t\t_opts: CoordinatorReviewJoinRequestInput,\n\t): Promise<CoordinatorJoinRequestReviewResult | null> {\n\t\tconst row = await firstRow<CoordinatorJoinRequest & { public_key: string }>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, device_id, public_key, fingerprint, display_name, token, status,\n\t\t\t\t\t created_at, reviewed_at, reviewed_by\n\t\t\t\t\t FROM coordinator_join_requests WHERE request_id = ?`)\n\t\t\t\t.bind(_opts.requestId),\n\t\t);\n\t\tif (!row) return null;\n\t\tif (row.status !== \"pending\") {\n\t\t\treturn { ...rowToRecord<CoordinatorJoinRequest>(row), _no_transition: true };\n\t\t}\n\t\tconst bootstrapGrantRequest = normalizeBootstrapGrantRequest(_opts.bootstrapGrant);\n\t\tconst reviewedAt = nowISO();\n\t\tconst nextStatus = _opts.approved ? \"approved\" : \"denied\";\n\t\tlet bootstrapGrantInput: CoordinatorCreateBootstrapGrantInput | null = null;\n\t\tlet bootstrapGrantId: string | null = null;\n\t\tif (_opts.approved && bootstrapGrantRequest) {\n\t\t\tconst seedEnrollment = await firstRow<{ device_id: string }>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`SELECT device_id FROM enrolled_devices WHERE group_id = ? AND device_id = ? AND enabled = 1`,\n\t\t\t\t\t)\n\t\t\t\t\t.bind(row.group_id, bootstrapGrantRequest.seedDeviceId),\n\t\t\t);\n\t\t\tif (!seedEnrollment) {\n\t\t\t\tthrow new Error(\"bootstrap grant seed device is not enrolled in the group.\");\n\t\t\t}\n\t\t\tif (bootstrapGrantRequest.seedDeviceId === row.device_id) {\n\t\t\t\tthrow new Error(\"bootstrap grant seed and worker device ids must differ.\");\n\t\t\t}\n\t\t\tbootstrapGrantInput = {\n\t\t\t\t...bootstrapGrantRequest,\n\t\t\t\tgroupId: row.group_id,\n\t\t\t\tworkerDeviceId: row.device_id,\n\t\t\t};\n\t\t}\n\t\tif (this.db.batch) {\n\t\t\tconst statements: D1PreparedStatementLike[] = [];\n\t\t\tstatements.push(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`UPDATE coordinator_join_requests\n\t\t\t\t\t\t SET status = ?, reviewed_at = ?, reviewed_by = ?\n\t\t\t\t\t\t WHERE request_id = ? AND status = 'pending'`)\n\t\t\t\t\t.bind(nextStatus, reviewedAt, _opts.reviewedBy ?? null, _opts.requestId),\n\t\t\t);\n\t\t\tif (_opts.approved) {\n\t\t\t\tstatements.push(\n\t\t\t\t\tthis.db\n\t\t\t\t\t\t.prepare(`INSERT INTO enrolled_devices(\n\t\t\t\t\t\t\tgroup_id, device_id, public_key, fingerprint, display_name, enabled, created_at\n\t\t\t\t\t\t)\n\t\t\t\t\t\tSELECT group_id, device_id, public_key, fingerprint, display_name, 1, ?\n\t\t\t\t\t\tFROM coordinator_join_requests\n\t\t\t\t\t\tWHERE request_id = ?\n\t\t\t\t\t\t AND status = 'approved'\n\t\t\t\t\t\t AND reviewed_at = ?\n\t\t\t\t\t\tON CONFLICT(group_id, device_id) DO UPDATE SET\n\t\t\t\t\t\t\tpublic_key = excluded.public_key,\n\t\t\t\t\t\t\tfingerprint = excluded.fingerprint,\n\t\t\t\t\t\t\tdisplay_name = excluded.display_name,\n\t\t\t\t\t\t\tenabled = 1`)\n\t\t\t\t\t\t.bind(nowISO(), _opts.requestId, reviewedAt),\n\t\t\t\t);\n\t\t\t\tif (bootstrapGrantInput) {\n\t\t\t\t\tbootstrapGrantId = tokenUrlSafe(12);\n\t\t\t\t\tconst createdAt = nowISO();\n\t\t\t\t\t// Conditional INSERT: only mint a grant if the join request was\n\t\t\t\t\t// actually transitioned to 'approved' by the UPDATE in this batch.\n\t\t\t\t\t// This prevents issuing extra grants under concurrent review races.\n\t\t\t\t\tstatements.push(\n\t\t\t\t\t\tthis.db\n\t\t\t\t\t\t\t.prepare(`INSERT INTO coordinator_bootstrap_grants(\n\t\t\t\t\t\t\tgrant_id, group_id, seed_device_id, worker_device_id, expires_at, created_at, created_by, revoked_at\n\t\t\t\t\t\t) SELECT ?, ?, ?, ?, ?, ?, ?, NULL\n\t\t\t\t\t\tWHERE EXISTS (\n\t\t\t\t\t\t\tSELECT 1 FROM coordinator_join_requests\n\t\t\t\t\t\t\tWHERE request_id = ? AND status = 'approved' AND reviewed_at = ?\n\t\t\t\t\t\t)`)\n\t\t\t\t\t\t\t.bind(\n\t\t\t\t\t\t\t\tbootstrapGrantId,\n\t\t\t\t\t\t\t\tbootstrapGrantInput.groupId,\n\t\t\t\t\t\t\t\tbootstrapGrantInput.seedDeviceId,\n\t\t\t\t\t\t\t\tbootstrapGrantInput.workerDeviceId,\n\t\t\t\t\t\t\t\tbootstrapGrantInput.expiresAt,\n\t\t\t\t\t\t\t\tcreatedAt,\n\t\t\t\t\t\t\t\tbootstrapGrantInput.createdBy ?? null,\n\t\t\t\t\t\t\t\t_opts.requestId,\n\t\t\t\t\t\t\t\treviewedAt,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait this.db.batch(statements);\n\t\t} else {\n\t\t\tlet createdGrantId: string | null = null;\n\t\t\tif (_opts.approved) {\n\t\t\t\tawait this.enrollDevice(row.group_id, {\n\t\t\t\t\tdeviceId: row.device_id,\n\t\t\t\t\tfingerprint: row.fingerprint,\n\t\t\t\t\tpublicKey: row.public_key,\n\t\t\t\t\tdisplayName: (row.display_name ?? \"\").trim() || null,\n\t\t\t\t});\n\t\t\t\tif (bootstrapGrantInput) {\n\t\t\t\t\tconst grant = await this.createBootstrapGrant(bootstrapGrantInput);\n\t\t\t\t\tcreatedGrantId = grant.grant_id;\n\t\t\t\t\tbootstrapGrantId = grant.grant_id;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst changes = await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`UPDATE coordinator_join_requests\n\t\t\t\t\t\t SET status = ?, reviewed_at = ?, reviewed_by = ?\n\t\t\t\t\t\t WHERE request_id = ? AND status = 'pending'`)\n\t\t\t\t\t.bind(nextStatus, reviewedAt, _opts.reviewedBy ?? null, _opts.requestId),\n\t\t\t);\n\t\t\tif (changes === 0) {\n\t\t\t\tif (createdGrantId) {\n\t\t\t\t\tawait this.revokeBootstrapGrant(createdGrantId);\n\t\t\t\t}\n\t\t\t\tconst latest = await firstRow<CoordinatorJoinRequestReviewResult>(\n\t\t\t\t\tthis.db\n\t\t\t\t\t\t.prepare(`SELECT request_id, group_id, device_id, public_key, fingerprint, display_name, token, status, created_at, reviewed_at, reviewed_by\n\t\t\t\t\t\t\t FROM coordinator_join_requests WHERE request_id = ?`)\n\t\t\t\t\t\t.bind(_opts.requestId),\n\t\t\t\t);\n\t\t\t\treturn latest\n\t\t\t\t\t? { ...rowToRecord<CoordinatorJoinRequestReviewResult>(latest), _no_transition: true }\n\t\t\t\t\t: null;\n\t\t\t}\n\t\t}\n\t\tconst updated = await firstRow<CoordinatorJoinRequestReviewResult>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, device_id, public_key, fingerprint, display_name, token, status, created_at, reviewed_at, reviewed_by\n\t\t\t\t\t FROM coordinator_join_requests WHERE request_id = ?`)\n\t\t\t\t.bind(_opts.requestId),\n\t\t);\n\t\tif (!updated) return null;\n\t\tconst bootstrapGrant = bootstrapGrantInput\n\t\t\t? bootstrapGrantId\n\t\t\t\t? await this.getBootstrapGrant(bootstrapGrantId)\n\t\t\t\t: await this.createBootstrapGrant(bootstrapGrantInput)\n\t\t\t: null;\n\t\treturn {\n\t\t\t...rowToRecord<CoordinatorJoinRequestReviewResult>(updated),\n\t\t\tbootstrap_grant: bootstrapGrant,\n\t\t};\n\t}\n\n\tasync createReciprocalApproval(\n\t\topts: CoordinatorCreateReciprocalApprovalInput,\n\t): Promise<CoordinatorReciprocalApproval> {\n\t\tconst groupId = opts.groupId.trim();\n\t\tconst requestingDeviceId = opts.requestingDeviceId.trim();\n\t\tconst requestedDeviceId = opts.requestedDeviceId.trim();\n\t\tif (!groupId || !requestingDeviceId || !requestedDeviceId) {\n\t\t\tthrow new Error(\"groupId, requestingDeviceId, and requestedDeviceId are required.\");\n\t\t}\n\t\tif (requestingDeviceId === requestedDeviceId) {\n\t\t\tthrow new Error(\"requesting and requested device ids must differ.\");\n\t\t}\n\t\tconst pendingPair = reciprocalPendingPair(requestingDeviceId, requestedDeviceId);\n\t\tconst existing = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t FROM coordinator_reciprocal_approvals\n\t\t\t\t\t WHERE group_id = ? AND requesting_device_id = ? AND requested_device_id = ? AND status = 'pending'\n\t\t\t\t\t ORDER BY created_at DESC LIMIT 1`)\n\t\t\t\t.bind(groupId, requestingDeviceId, requestedDeviceId),\n\t\t);\n\t\tif (existing) return rowToRecord<CoordinatorReciprocalApproval>(existing);\n\t\tconst reverse = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t FROM coordinator_reciprocal_approvals\n\t\t\t\t\t WHERE group_id = ? AND requesting_device_id = ? AND requested_device_id = ? AND status = 'pending'\n\t\t\t\t\t ORDER BY created_at DESC LIMIT 1`)\n\t\t\t\t.bind(groupId, requestedDeviceId, requestingDeviceId),\n\t\t);\n\t\tif (reverse) {\n\t\t\tconst resolvedAt = nowISO();\n\t\t\tawait this.db\n\t\t\t\t.prepare(\n\t\t\t\t\t`UPDATE coordinator_reciprocal_approvals SET status = 'completed', resolved_at = ? WHERE request_id = ?`,\n\t\t\t\t)\n\t\t\t\t.bind(resolvedAt, reverse.request_id)\n\t\t\t\t.run();\n\t\t\tconst completed = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t\t FROM coordinator_reciprocal_approvals WHERE request_id = ?`)\n\t\t\t\t\t.bind(reverse.request_id),\n\t\t\t);\n\t\t\treturn rowToRecord<CoordinatorReciprocalApproval>(completed);\n\t\t}\n\t\tconst requestId = tokenUrlSafe(12);\n\t\tconst createdAt = nowISO();\n\t\ttry {\n\t\t\tawait this.db\n\t\t\t\t.prepare(`INSERT INTO coordinator_reciprocal_approvals(\n\t\t\t\t\t\trequest_id,\n\t\t\t\t\t\tgroup_id,\n\t\t\t\t\t\trequesting_device_id,\n\t\t\t\t\t\trequested_device_id,\n\t\t\t\t\t\tpending_pair_low_device_id,\n\t\t\t\t\t\tpending_pair_high_device_id,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\tcreated_at,\n\t\t\t\t\t\tresolved_at\n\t\t\t\t\t) VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, NULL)`)\n\t\t\t\t.bind(\n\t\t\t\t\trequestId,\n\t\t\t\t\tgroupId,\n\t\t\t\t\trequestingDeviceId,\n\t\t\t\t\trequestedDeviceId,\n\t\t\t\t\tpendingPair.low,\n\t\t\t\t\tpendingPair.high,\n\t\t\t\t\tcreatedAt,\n\t\t\t\t)\n\t\t\t\t.run();\n\t\t} catch (error) {\n\t\t\tif (!isUniqueConstraintError(error)) throw error;\n\t\t\tconst sameDirection = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t\t FROM coordinator_reciprocal_approvals\n\t\t\t\t\t\t WHERE group_id = ? AND requesting_device_id = ? AND requested_device_id = ? AND status = 'pending'\n\t\t\t\t\t\t ORDER BY created_at DESC LIMIT 1`)\n\t\t\t\t\t.bind(groupId, requestingDeviceId, requestedDeviceId),\n\t\t\t);\n\t\t\tif (sameDirection) return rowToRecord<CoordinatorReciprocalApproval>(sameDirection);\n\t\t\tconst reverseAfterConflict = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t\t FROM coordinator_reciprocal_approvals\n\t\t\t\t\t\t WHERE group_id = ? AND requesting_device_id = ? AND requested_device_id = ? AND status = 'pending'\n\t\t\t\t\t\t ORDER BY created_at DESC LIMIT 1`)\n\t\t\t\t\t.bind(groupId, requestedDeviceId, requestingDeviceId),\n\t\t\t);\n\t\t\tif (reverseAfterConflict) {\n\t\t\t\tconst resolvedAt = nowISO();\n\t\t\t\tawait this.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`UPDATE coordinator_reciprocal_approvals SET status = 'completed', resolved_at = ? WHERE request_id = ?`,\n\t\t\t\t\t)\n\t\t\t\t\t.bind(resolvedAt, reverseAfterConflict.request_id)\n\t\t\t\t\t.run();\n\t\t\t\tconst completed = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\t\t\tthis.db\n\t\t\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t\t\t FROM coordinator_reciprocal_approvals WHERE request_id = ?`)\n\t\t\t\t\t\t.bind(reverseAfterConflict.request_id),\n\t\t\t\t);\n\t\t\t\treturn rowToRecord<CoordinatorReciprocalApproval>(completed);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tconst created = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t FROM coordinator_reciprocal_approvals WHERE request_id = ?`)\n\t\t\t\t.bind(requestId),\n\t\t);\n\t\treturn rowToRecord<CoordinatorReciprocalApproval>(created);\n\t}\n\n\tasync createBootstrapGrant(\n\t\topts: CoordinatorCreateBootstrapGrantInput,\n\t): Promise<CoordinatorBootstrapGrant> {\n\t\tconst normalized = requireTrimmedBootstrapGrantInput(opts);\n\t\tconst grantId = tokenUrlSafe(12);\n\t\tconst createdAt = nowISO();\n\t\tawait this.db\n\t\t\t.prepare(`INSERT INTO coordinator_bootstrap_grants(\n\t\t\t\tgrant_id, group_id, seed_device_id, worker_device_id, expires_at, created_at, created_by, revoked_at\n\t\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, NULL)`)\n\t\t\t.bind(\n\t\t\t\tgrantId,\n\t\t\t\tnormalized.groupId,\n\t\t\t\tnormalized.seedDeviceId,\n\t\t\t\tnormalized.workerDeviceId,\n\t\t\t\tnormalized.expiresAt,\n\t\t\t\tcreatedAt,\n\t\t\t\tnormalized.createdBy,\n\t\t\t)\n\t\t\t.run();\n\t\tconst row = await firstRow<CoordinatorBootstrapGrant>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT grant_id, group_id, seed_device_id, worker_device_id, expires_at, created_at, created_by, revoked_at\n\t\t\t\t\t FROM coordinator_bootstrap_grants WHERE grant_id = ?`)\n\t\t\t\t.bind(grantId),\n\t\t);\n\t\treturn rowToRecord<CoordinatorBootstrapGrant>(row);\n\t}\n\n\tasync getBootstrapGrant(grantId: string): Promise<CoordinatorBootstrapGrant | null> {\n\t\tconst row = await firstRow<CoordinatorBootstrapGrant>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT grant_id, group_id, seed_device_id, worker_device_id, expires_at, created_at, created_by, revoked_at\n\t\t\t\t\t FROM coordinator_bootstrap_grants WHERE grant_id = ?`)\n\t\t\t\t.bind(grantId),\n\t\t);\n\t\treturn row ? rowToRecord<CoordinatorBootstrapGrant>(row) : null;\n\t}\n\n\tasync listBootstrapGrants(groupId: string): Promise<CoordinatorBootstrapGrant[]> {\n\t\treturn (\n\t\t\tawait allRows<CoordinatorBootstrapGrant>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT grant_id, group_id, seed_device_id, worker_device_id, expires_at, created_at, created_by, revoked_at\n\t\t\t\t\t\t FROM coordinator_bootstrap_grants WHERE group_id = ?\n\t\t\t\t\t\t ORDER BY created_at DESC, grant_id DESC`)\n\t\t\t\t\t.bind(groupId),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorBootstrapGrant>(row));\n\t}\n\n\tasync revokeBootstrapGrant(grantId: string, revokedAt = nowISO()): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`UPDATE coordinator_bootstrap_grants\n\t\t\t\t\t\t SET revoked_at = COALESCE(revoked_at, ?)\n\t\t\t\t\t\t WHERE grant_id = ?`)\n\t\t\t\t\t.bind(revokedAt, grantId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync listReciprocalApprovals(\n\t\topts: CoordinatorListReciprocalApprovalsInput,\n\t): Promise<CoordinatorReciprocalApproval[]> {\n\t\tconst directionColumn =\n\t\t\topts.direction === \"incoming\" ? \"requested_device_id\" : \"requesting_device_id\";\n\t\tconst status = opts.status?.trim() || \"pending\";\n\t\treturn (\n\t\t\tawait allRows<CoordinatorReciprocalApproval>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t\t FROM coordinator_reciprocal_approvals\n\t\t\t\t\t\t WHERE group_id = ? AND ${directionColumn} = ? AND status = ?\n\t\t\t\t\t\t ORDER BY created_at ASC, request_id ASC`)\n\t\t\t\t\t.bind(opts.groupId, opts.deviceId, status),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorReciprocalApproval>(row));\n\t}\n\n\tasync upsertPresence(_opts: CoordinatorUpsertPresenceInput): Promise<CoordinatorPresenceRecord> {\n\t\tconst now = new Date();\n\t\tconst expiresAt = new Date(now.getTime() + _opts.ttlS * 1000).toISOString();\n\t\tconst normalized = mergeAddresses([], _opts.addresses);\n\t\tawait this.db\n\t\t\t.prepare(`INSERT INTO presence_records(group_id, device_id, addresses_json, last_seen_at, expires_at, capabilities_json)\n\t\t\t\t VALUES (?, ?, ?, ?, ?, ?)\n\t\t\t\t ON CONFLICT(group_id, device_id) DO UPDATE SET\n\t\t\t\t\taddresses_json = excluded.addresses_json,\n\t\t\t\t\tlast_seen_at = excluded.last_seen_at,\n\t\t\t\t\texpires_at = excluded.expires_at,\n\t\t\t\t\tcapabilities_json = excluded.capabilities_json`)\n\t\t\t.bind(\n\t\t\t\t_opts.groupId,\n\t\t\t\t_opts.deviceId,\n\t\t\t\tJSON.stringify(normalized),\n\t\t\t\tnow.toISOString(),\n\t\t\t\texpiresAt,\n\t\t\t\tJSON.stringify(_opts.capabilities ?? {}),\n\t\t\t)\n\t\t\t.run();\n\t\treturn {\n\t\t\tgroup_id: _opts.groupId,\n\t\t\tdevice_id: _opts.deviceId,\n\t\t\taddresses: normalized,\n\t\t\texpires_at: expiresAt,\n\t\t};\n\t}\n\n\tasync listGroupPeers(\n\t\t_groupId: string,\n\t\t_requestingDeviceId: string,\n\t): Promise<CoordinatorPeerRecord[]> {\n\t\tconst now = nowISO();\n\t\tconst rows = await allRows<Record<string, unknown>>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT enrolled_devices.device_id, enrolled_devices.public_key, enrolled_devices.fingerprint, enrolled_devices.display_name,\n\t\t\t\t\t\tpresence_records.addresses_json, presence_records.last_seen_at, presence_records.expires_at,\n\t\t\t\t\t\tpresence_records.capabilities_json\n\t\t\t\t\t FROM enrolled_devices\n\t\t\t\t\t LEFT JOIN presence_records\n\t\t\t\t\t ON presence_records.group_id = enrolled_devices.group_id\n\t\t\t\t\t AND presence_records.device_id = enrolled_devices.device_id\n\t\t\t\t\t WHERE enrolled_devices.group_id = ?\n\t\t\t\t\t AND enrolled_devices.enabled = 1\n\t\t\t\t\t AND enrolled_devices.device_id != ?\n\t\t\t\t\t ORDER BY enrolled_devices.device_id ASC`)\n\t\t\t\t.bind(_groupId, _requestingDeviceId),\n\t\t);\n\t\treturn rows.map((row) => {\n\t\t\tconst expiresRaw = String(row.expires_at ?? \"\").trim();\n\t\t\tlet stale = true;\n\t\t\tif (expiresRaw) {\n\t\t\t\tconst expiresAt = new Date(expiresRaw);\n\t\t\t\tstale = Number.isNaN(expiresAt.getTime()) || expiresAt.toISOString() <= now;\n\t\t\t}\n\t\t\tconst addresses = stale\n\t\t\t\t? []\n\t\t\t\t: mergeAddresses([], JSON.parse(String(row.addresses_json ?? \"[]\")) as string[]);\n\t\t\treturn {\n\t\t\t\tdevice_id: String(row.device_id ?? \"\"),\n\t\t\t\tpublic_key: String(row.public_key ?? \"\"),\n\t\t\t\tfingerprint: String(row.fingerprint ?? \"\"),\n\t\t\t\tdisplay_name: (row.display_name as string | null) ?? null,\n\t\t\t\taddresses,\n\t\t\t\tlast_seen_at: (row.last_seen_at as string | null) ?? null,\n\t\t\t\texpires_at: (row.expires_at as string | null) ?? null,\n\t\t\t\tstale,\n\t\t\t\tcapabilities: JSON.parse(String(row.capabilities_json ?? \"{}\")) as Record<string, unknown>,\n\t\t\t} satisfies CoordinatorPeerRecord;\n\t\t});\n\t}\n}\n","import {\n\ttype CoordinatorRequestVerifier,\n\ttype CoordinatorRuntimeDeps,\n\tcreateCoordinatorApp,\n} from \"./coordinator-api.js\";\nimport { D1CoordinatorStore, type D1DatabaseLike } from \"./d1-coordinator-store.js\";\n\nexport interface CreateD1CoordinatorAppOptions {\n\tdb: D1DatabaseLike;\n\tadminSecret?: string | null;\n\tnow?: () => string;\n\trequestVerifier: CoordinatorRequestVerifier;\n}\n\nexport function createD1CoordinatorApp(\n\topts: CreateD1CoordinatorAppOptions,\n): ReturnType<typeof createCoordinatorApp> {\n\tconst runtime: CoordinatorRuntimeDeps = {\n\t\tadminSecret: () => String(opts.adminSecret ?? \"\").trim() || null,\n\t\tnow: opts.now ?? (() => new Date().toISOString()),\n\t};\n\treturn createCoordinatorApp({\n\t\tstoreFactory: () => new D1CoordinatorStore(opts.db),\n\t\truntime,\n\t\trequestVerifier: opts.requestVerifier,\n\t});\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cloudflare-coordinator.js","names":[],"sources":["../../src/d1-coordinator-store.ts","../../src/d1-coordinator-runtime.ts"],"sourcesContent":["import type {\n\tCoordinatorBootstrapGrant,\n\tCoordinatorCreateBootstrapGrantInput,\n\tCoordinatorCreateInviteInput,\n\tCoordinatorCreateJoinRequestInput,\n\tCoordinatorCreateReciprocalApprovalInput,\n\tCoordinatorEnrollDeviceInput,\n\tCoordinatorEnrollment,\n\tCoordinatorGroup,\n\tCoordinatorInvite,\n\tCoordinatorJoinRequest,\n\tCoordinatorJoinRequestReviewResult,\n\tCoordinatorListReciprocalApprovalsInput,\n\tCoordinatorPeerRecord,\n\tCoordinatorPresenceRecord,\n\tCoordinatorReciprocalApproval,\n\tCoordinatorReviewJoinRequestBootstrapGrantInput,\n\tCoordinatorReviewJoinRequestInput,\n\tCoordinatorStore,\n\tCoordinatorUpsertPresenceInput,\n} from \"./coordinator-store-contract.js\";\n\ninterface D1RunResultLike {\n\tmeta?: {\n\t\tchanges?: number;\n\t};\n}\n\n/**\n * Experimental D1 adapter scaffold.\n *\n * This is intentionally internal for now while the sync-only store contract\n * is still being validated against an async backend shape.\n */\n\nexport interface D1PreparedStatementLike {\n\tbind(...values: unknown[]): D1PreparedStatementLike;\n\tfirst<T = unknown>(): Promise<T | null>;\n\trun(): Promise<unknown>;\n\tall<T = unknown>(): Promise<{ results?: T[] }>;\n\traw<T = unknown>(): Promise<T[]>;\n}\n\nexport interface D1DatabaseLike {\n\tprepare(query: string): D1PreparedStatementLike;\n\tbatch?(statements: D1PreparedStatementLike[]): Promise<unknown[]>;\n\texec?(query: string): Promise<unknown>;\n}\n\nfunction rowToRecord<T>(row: unknown): T {\n\tif (row == null) throw new Error(\"expected row\");\n\treturn row as T;\n}\n\nfunction normalizeAddress(address: string): string {\n\tconst value = address.trim();\n\tif (!value) return \"\";\n\tconst withScheme = value.includes(\"://\") ? value : `http://${value}`;\n\ttry {\n\t\tconst url = new URL(withScheme);\n\t\tif (!url.hostname) return \"\";\n\t\tif (url.port && (Number(url.port) <= 0 || Number(url.port) > 65535)) return \"\";\n\t\treturn url.origin + url.pathname.replace(/\\/+$/, \"\");\n\t} catch {\n\t\treturn \"\";\n\t}\n}\n\nfunction addressDedupeKey(address: string): string {\n\tif (!address) return \"\";\n\ttry {\n\t\tconst parsed = new URL(address);\n\t\tconst host = parsed.hostname.toLowerCase();\n\t\tif (\n\t\t\t(parsed.protocol === \"http:\" || parsed.protocol === \"\") &&\n\t\t\thost &&\n\t\t\tparsed.port &&\n\t\t\tparsed.pathname === \"/\"\n\t\t) {\n\t\t\treturn `${host}:${parsed.port}`;\n\t\t}\n\t} catch {}\n\treturn address;\n}\n\nfunction mergeAddresses(existing: string[], candidates: string[]): string[] {\n\tconst normalized: string[] = [];\n\tconst seen = new Set<string>();\n\tfor (const address of [...existing, ...candidates]) {\n\t\tconst cleaned = normalizeAddress(address);\n\t\tconst key = addressDedupeKey(cleaned);\n\t\tif (!cleaned || seen.has(key)) continue;\n\t\tseen.add(key);\n\t\tnormalized.push(cleaned);\n\t}\n\treturn normalized;\n}\n\nfunction nowISO(): string {\n\treturn new Date().toISOString();\n}\n\nfunction reciprocalPendingPair(\n\trequestingDeviceId: string,\n\trequestedDeviceId: string,\n): {\n\tlow: string;\n\thigh: string;\n} {\n\treturn requestingDeviceId <= requestedDeviceId\n\t\t? { low: requestingDeviceId, high: requestedDeviceId }\n\t\t: { low: requestedDeviceId, high: requestingDeviceId };\n}\n\nfunction isUniqueConstraintError(error: unknown): boolean {\n\treturn error instanceof Error && /unique|constraint/i.test(error.message);\n}\n\nfunction tokenUrlSafe(bytes: number): string {\n\tconst alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n\tconst random = new Uint8Array(bytes);\n\tglobalThis.crypto.getRandomValues(random);\n\tconst output: string[] = [];\n\tfor (const byte of random) {\n\t\toutput.push(alphabet[byte % alphabet.length] ?? \"A\");\n\t}\n\treturn output.join(\"\");\n}\n\nfunction requireTrimmedBootstrapGrantInput(opts: CoordinatorCreateBootstrapGrantInput): {\n\tgroupId: string;\n\tseedDeviceId: string;\n\tworkerDeviceId: string;\n\texpiresAt: string;\n\tcreatedBy: string | null;\n} {\n\tconst groupId = String(opts.groupId ?? \"\").trim();\n\tconst seedDeviceId = String(opts.seedDeviceId ?? \"\").trim();\n\tconst workerDeviceId = String(opts.workerDeviceId ?? \"\").trim();\n\tconst expiresAt = String(opts.expiresAt ?? \"\").trim();\n\tconst createdBy = String(opts.createdBy ?? \"\").trim() || null;\n\tif (!groupId || !seedDeviceId || !workerDeviceId || !expiresAt) {\n\t\tthrow new Error(\"groupId, seedDeviceId, workerDeviceId, and expiresAt are required.\");\n\t}\n\treturn { groupId, seedDeviceId, workerDeviceId, expiresAt, createdBy };\n}\n\nfunction normalizeBootstrapGrantRequest(\n\tinput: CoordinatorReviewJoinRequestBootstrapGrantInput | null | undefined,\n): CoordinatorCreateBootstrapGrantInput | null {\n\tif (!input) return null;\n\tconst seedDeviceId = String(input.seedDeviceId ?? \"\").trim();\n\tconst expiresAt = String(input.expiresAt ?? \"\").trim();\n\tconst createdBy = String(input.createdBy ?? \"\").trim() || null;\n\tif (!seedDeviceId || !expiresAt) {\n\t\tthrow new Error(\"bootstrapGrant.seedDeviceId and expiresAt are required.\");\n\t}\n\treturn {\n\t\tgroupId: \"\",\n\t\tseedDeviceId,\n\t\tworkerDeviceId: \"\",\n\t\texpiresAt,\n\t\tcreatedBy,\n\t};\n}\n\nasync function allRows<T>(statement: D1PreparedStatementLike): Promise<T[]> {\n\tconst result = await statement.all<T>();\n\treturn Array.isArray(result?.results) ? result.results : [];\n}\n\nasync function firstRow<T>(statement: D1PreparedStatementLike): Promise<T | null> {\n\treturn await statement.first<T>();\n}\n\nasync function runChanges(statement: D1PreparedStatementLike): Promise<number> {\n\tconst result = (await statement.run()) as D1RunResultLike | undefined;\n\treturn Number(result?.meta?.changes ?? 0);\n}\n\nexport class D1CoordinatorStore implements CoordinatorStore {\n\treadonly db: D1DatabaseLike;\n\n\tconstructor(db: D1DatabaseLike) {\n\t\tthis.db = db;\n\t}\n\n\tasync close(): Promise<void> {\n\t\t// No-op for D1 bindings.\n\t}\n\n\tasync createGroup(_groupId: string, _displayName?: string | null): Promise<void> {\n\t\tawait this.db\n\t\t\t.prepare(\n\t\t\t\t\"INSERT OR IGNORE INTO groups(group_id, display_name, archived_at, created_at) VALUES (?, ?, NULL, ?)\",\n\t\t\t)\n\t\t\t.bind(_groupId, _displayName ?? null, nowISO())\n\t\t\t.run();\n\t}\n\n\tasync getGroup(_groupId: string): Promise<CoordinatorGroup | null> {\n\t\tconst row = await firstRow<CoordinatorGroup>(\n\t\t\tthis.db\n\t\t\t\t.prepare(\n\t\t\t\t\t\"SELECT group_id, display_name, archived_at, created_at FROM groups WHERE group_id = ?\",\n\t\t\t\t)\n\t\t\t\t.bind(_groupId),\n\t\t);\n\t\treturn row ? rowToRecord<CoordinatorGroup>(row) : null;\n\t}\n\n\tasync listGroups(_includeArchived = false): Promise<CoordinatorGroup[]> {\n\t\tconst where = _includeArchived ? \"\" : \"WHERE archived_at IS NULL\";\n\t\treturn (\n\t\t\tawait allRows<CoordinatorGroup>(\n\t\t\t\tthis.db.prepare(\n\t\t\t\t\t`SELECT group_id, display_name, archived_at, created_at FROM groups ${where} ORDER BY created_at ASC`,\n\t\t\t\t),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorGroup>(row));\n\t}\n\n\tasync renameGroup(_groupId: string, _displayName: string): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\"UPDATE groups SET display_name = ? WHERE group_id = ?\")\n\t\t\t\t\t.bind(_displayName, _groupId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync archiveGroup(_groupId: string, _archivedAt = nowISO()): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\"UPDATE groups SET archived_at = ? WHERE group_id = ? AND archived_at IS NULL\")\n\t\t\t\t\t.bind(_archivedAt, _groupId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync unarchiveGroup(_groupId: string): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t\"UPDATE groups SET archived_at = NULL WHERE group_id = ? AND archived_at IS NOT NULL\",\n\t\t\t\t\t)\n\t\t\t\t\t.bind(_groupId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync enrollDevice(_groupId: string, _opts: CoordinatorEnrollDeviceInput): Promise<void> {\n\t\tawait this.db\n\t\t\t.prepare(`INSERT INTO enrolled_devices(\n\t\t\t\tgroup_id, device_id, public_key, fingerprint, display_name, enabled, created_at\n\t\t\t) VALUES (?, ?, ?, ?, ?, 1, ?)\n\t\t\tON CONFLICT(group_id, device_id) DO UPDATE SET\n\t\t\t\tpublic_key = excluded.public_key,\n\t\t\t\tfingerprint = excluded.fingerprint,\n\t\t\t\tdisplay_name = excluded.display_name,\n\t\t\t\tenabled = 1`)\n\t\t\t.bind(\n\t\t\t\t_groupId,\n\t\t\t\t_opts.deviceId,\n\t\t\t\t_opts.publicKey,\n\t\t\t\t_opts.fingerprint,\n\t\t\t\t_opts.displayName ?? null,\n\t\t\t\tnowISO(),\n\t\t\t)\n\t\t\t.run();\n\t}\n\n\tasync listEnrolledDevices(\n\t\t_groupId: string,\n\t\t_includeDisabled?: boolean,\n\t): Promise<CoordinatorEnrollment[]> {\n\t\tconst where = _includeDisabled ? \"\" : \"AND enabled = 1\";\n\t\treturn (\n\t\t\tawait allRows<CoordinatorEnrollment>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT group_id, device_id, public_key, fingerprint, display_name, enabled, created_at\n\t\t\t\t\t\t FROM enrolled_devices\n\t\t\t\t\t\t WHERE group_id = ? ${where}\n\t\t\t\t\t\t ORDER BY created_at ASC, device_id ASC`)\n\t\t\t\t\t.bind(_groupId),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorEnrollment>(row));\n\t}\n\n\tasync getEnrollment(_groupId: string, _deviceId: string): Promise<CoordinatorEnrollment | null> {\n\t\tconst row = await firstRow<CoordinatorEnrollment>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT group_id, device_id, public_key, fingerprint, display_name, enabled, created_at\n\t\t\t\t\t FROM enrolled_devices\n\t\t\t\t\t WHERE group_id = ? AND device_id = ? AND enabled = 1`)\n\t\t\t\t.bind(_groupId, _deviceId),\n\t\t);\n\t\treturn row ? rowToRecord<CoordinatorEnrollment>(row) : null;\n\t}\n\n\tasync renameDevice(_groupId: string, _deviceId: string, _displayName: string): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`UPDATE enrolled_devices SET display_name = ?\n\t\t\t\t\t WHERE group_id = ? AND device_id = ?`)\n\t\t\t\t\t.bind(_displayName, _groupId, _deviceId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync setDeviceEnabled(_groupId: string, _deviceId: string, _enabled: boolean): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`UPDATE enrolled_devices SET enabled = ?\n\t\t\t\t\t WHERE group_id = ? AND device_id = ?`)\n\t\t\t\t\t.bind(_enabled ? 1 : 0, _groupId, _deviceId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync removeDevice(_groupId: string, _deviceId: string): Promise<boolean> {\n\t\tawait this.db\n\t\t\t.prepare(\"DELETE FROM presence_records WHERE group_id = ? AND device_id = ?\")\n\t\t\t.bind(_groupId, _deviceId)\n\t\t\t.run();\n\t\tawait this.db\n\t\t\t.prepare(\n\t\t\t\t\"DELETE FROM coordinator_reciprocal_approvals WHERE group_id = ? AND (requesting_device_id = ? OR requested_device_id = ?)\",\n\t\t\t)\n\t\t\t.bind(_groupId, _deviceId, _deviceId)\n\t\t\t.run();\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\"DELETE FROM enrolled_devices WHERE group_id = ? AND device_id = ?\")\n\t\t\t\t\t.bind(_groupId, _deviceId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync recordNonce(_deviceId: string, _nonce: string, _createdAt: string): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t\"INSERT OR IGNORE INTO request_nonces(device_id, nonce, created_at) VALUES (?, ?, ?)\",\n\t\t\t\t\t)\n\t\t\t\t\t.bind(_deviceId, _nonce, _createdAt),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync cleanupNonces(_cutoff: string): Promise<void> {\n\t\tawait this.db.prepare(\"DELETE FROM request_nonces WHERE created_at < ?\").bind(_cutoff).run();\n\t}\n\n\tasync createInvite(_opts: CoordinatorCreateInviteInput): Promise<CoordinatorInvite> {\n\t\tconst now = nowISO();\n\t\tconst inviteId = tokenUrlSafe(12);\n\t\tconst token = tokenUrlSafe(24);\n\t\tconst group = await this.getGroup(_opts.groupId);\n\t\tawait this.db\n\t\t\t.prepare(`INSERT INTO coordinator_invites(\n\t\t\t\tinvite_id, group_id, token, policy, expires_at, created_at, created_by, team_name_snapshot, revoked_at\n\t\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)`)\n\t\t\t.bind(\n\t\t\t\tinviteId,\n\t\t\t\t_opts.groupId,\n\t\t\t\ttoken,\n\t\t\t\t_opts.policy,\n\t\t\t\t_opts.expiresAt,\n\t\t\t\tnow,\n\t\t\t\t_opts.createdBy ?? null,\n\t\t\t\tgroup?.display_name ?? null,\n\t\t\t)\n\t\t\t.run();\n\t\tconst row = await firstRow<CoordinatorInvite>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT invite_id, group_id, token, policy, expires_at, created_at, created_by, team_name_snapshot, revoked_at\n\t\t\t\t\t FROM coordinator_invites WHERE invite_id = ?`)\n\t\t\t\t.bind(inviteId),\n\t\t);\n\t\treturn rowToRecord<CoordinatorInvite>(row);\n\t}\n\n\tasync getInviteByToken(_token: string): Promise<CoordinatorInvite | null> {\n\t\tconst row = await firstRow<CoordinatorInvite>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT invite_id, group_id, token, policy, expires_at, created_at, created_by, team_name_snapshot, revoked_at\n\t\t\t\t\t FROM coordinator_invites\n\t\t\t\t\t WHERE token = ?\n\t\t\t\t\t AND revoked_at IS NULL\n\t\t\t\t\t AND expires_at > ?`)\n\t\t\t\t.bind(_token, nowISO()),\n\t\t);\n\t\treturn row ? rowToRecord<CoordinatorInvite>(row) : null;\n\t}\n\n\tasync listInvites(_groupId: string): Promise<CoordinatorInvite[]> {\n\t\treturn (\n\t\t\tawait allRows<CoordinatorInvite>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT invite_id, group_id, token, policy, expires_at, created_at, created_by, team_name_snapshot, revoked_at\n\t\t\t\t\t FROM coordinator_invites WHERE group_id = ?\n\t\t\t\t\t ORDER BY created_at DESC`)\n\t\t\t\t\t.bind(_groupId),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorInvite>(row));\n\t}\n\n\tasync createJoinRequest(\n\t\t_opts: CoordinatorCreateJoinRequestInput,\n\t): Promise<CoordinatorJoinRequest> {\n\t\tconst now = nowISO();\n\t\tconst requestId = tokenUrlSafe(12);\n\t\tawait this.db\n\t\t\t.prepare(`INSERT INTO coordinator_join_requests(\n\t\t\t\trequest_id, group_id, device_id, public_key, fingerprint, display_name, token, status, created_at, reviewed_at, reviewed_by\n\t\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, NULL, NULL)`)\n\t\t\t.bind(\n\t\t\t\trequestId,\n\t\t\t\t_opts.groupId,\n\t\t\t\t_opts.deviceId,\n\t\t\t\t_opts.publicKey,\n\t\t\t\t_opts.fingerprint,\n\t\t\t\t_opts.displayName ?? null,\n\t\t\t\t_opts.token,\n\t\t\t\tnow,\n\t\t\t)\n\t\t\t.run();\n\t\tconst row = await firstRow<CoordinatorJoinRequest>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, device_id, public_key, fingerprint, display_name, token, status, created_at, reviewed_at, reviewed_by\n\t\t\t\t\t FROM coordinator_join_requests WHERE request_id = ?`)\n\t\t\t\t.bind(requestId),\n\t\t);\n\t\treturn rowToRecord<CoordinatorJoinRequest>(row);\n\t}\n\n\tasync listJoinRequests(_groupId: string, _status?: string): Promise<CoordinatorJoinRequest[]> {\n\t\tconst status = _status ?? \"pending\";\n\t\treturn (\n\t\t\tawait allRows<CoordinatorJoinRequest>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT request_id, group_id, device_id, public_key, fingerprint, display_name, token, status, created_at, reviewed_at, reviewed_by\n\t\t\t\t\t\t FROM coordinator_join_requests\n\t\t\t\t\t\t WHERE group_id = ? AND status = ?\n\t\t\t\t\t\t ORDER BY created_at ASC, device_id ASC`)\n\t\t\t\t\t.bind(_groupId, status),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorJoinRequest>(row));\n\t}\n\n\tasync reviewJoinRequest(\n\t\t_opts: CoordinatorReviewJoinRequestInput,\n\t): Promise<CoordinatorJoinRequestReviewResult | null> {\n\t\tconst row = await firstRow<CoordinatorJoinRequest & { public_key: string }>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, device_id, public_key, fingerprint, display_name, token, status,\n\t\t\t\t\t created_at, reviewed_at, reviewed_by\n\t\t\t\t\t FROM coordinator_join_requests WHERE request_id = ?`)\n\t\t\t\t.bind(_opts.requestId),\n\t\t);\n\t\tif (!row) return null;\n\t\tif (row.status !== \"pending\") {\n\t\t\treturn { ...rowToRecord<CoordinatorJoinRequest>(row), _no_transition: true };\n\t\t}\n\t\tconst bootstrapGrantRequest = normalizeBootstrapGrantRequest(_opts.bootstrapGrant);\n\t\tconst reviewedAt = nowISO();\n\t\tconst nextStatus = _opts.approved ? \"approved\" : \"denied\";\n\t\tlet bootstrapGrantInput: CoordinatorCreateBootstrapGrantInput | null = null;\n\t\tlet bootstrapGrantId: string | null = null;\n\t\tif (_opts.approved && bootstrapGrantRequest) {\n\t\t\tconst seedEnrollment = await firstRow<{ device_id: string }>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`SELECT device_id FROM enrolled_devices WHERE group_id = ? AND device_id = ? AND enabled = 1`,\n\t\t\t\t\t)\n\t\t\t\t\t.bind(row.group_id, bootstrapGrantRequest.seedDeviceId),\n\t\t\t);\n\t\t\tif (!seedEnrollment) {\n\t\t\t\tthrow new Error(\"bootstrap grant seed device is not enrolled in the group.\");\n\t\t\t}\n\t\t\tif (bootstrapGrantRequest.seedDeviceId === row.device_id) {\n\t\t\t\tthrow new Error(\"bootstrap grant seed and worker device ids must differ.\");\n\t\t\t}\n\t\t\tbootstrapGrantInput = {\n\t\t\t\t...bootstrapGrantRequest,\n\t\t\t\tgroupId: row.group_id,\n\t\t\t\tworkerDeviceId: row.device_id,\n\t\t\t};\n\t\t}\n\t\tif (this.db.batch) {\n\t\t\tconst statements: D1PreparedStatementLike[] = [];\n\t\t\tstatements.push(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`UPDATE coordinator_join_requests\n\t\t\t\t\t\t SET status = ?, reviewed_at = ?, reviewed_by = ?\n\t\t\t\t\t\t WHERE request_id = ? AND status = 'pending'`)\n\t\t\t\t\t.bind(nextStatus, reviewedAt, _opts.reviewedBy ?? null, _opts.requestId),\n\t\t\t);\n\t\t\tif (_opts.approved) {\n\t\t\t\tstatements.push(\n\t\t\t\t\tthis.db\n\t\t\t\t\t\t.prepare(`INSERT INTO enrolled_devices(\n\t\t\t\t\t\t\tgroup_id, device_id, public_key, fingerprint, display_name, enabled, created_at\n\t\t\t\t\t\t)\n\t\t\t\t\t\tSELECT group_id, device_id, public_key, fingerprint, display_name, 1, ?\n\t\t\t\t\t\tFROM coordinator_join_requests\n\t\t\t\t\t\tWHERE request_id = ?\n\t\t\t\t\t\t AND status = 'approved'\n\t\t\t\t\t\t AND reviewed_at = ?\n\t\t\t\t\t\tON CONFLICT(group_id, device_id) DO UPDATE SET\n\t\t\t\t\t\t\tpublic_key = excluded.public_key,\n\t\t\t\t\t\t\tfingerprint = excluded.fingerprint,\n\t\t\t\t\t\t\tdisplay_name = excluded.display_name,\n\t\t\t\t\t\t\tenabled = 1`)\n\t\t\t\t\t\t.bind(nowISO(), _opts.requestId, reviewedAt),\n\t\t\t\t);\n\t\t\t\tif (bootstrapGrantInput) {\n\t\t\t\t\tbootstrapGrantId = tokenUrlSafe(12);\n\t\t\t\t\tconst createdAt = nowISO();\n\t\t\t\t\t// Conditional INSERT: only mint a grant if the join request was\n\t\t\t\t\t// actually transitioned to 'approved' by the UPDATE in this batch.\n\t\t\t\t\t// This prevents issuing extra grants under concurrent review races.\n\t\t\t\t\tstatements.push(\n\t\t\t\t\t\tthis.db\n\t\t\t\t\t\t\t.prepare(`INSERT INTO coordinator_bootstrap_grants(\n\t\t\t\t\t\t\tgrant_id, group_id, seed_device_id, worker_device_id, expires_at, created_at, created_by, revoked_at\n\t\t\t\t\t\t) SELECT ?, ?, ?, ?, ?, ?, ?, NULL\n\t\t\t\t\t\tWHERE EXISTS (\n\t\t\t\t\t\t\tSELECT 1 FROM coordinator_join_requests\n\t\t\t\t\t\t\tWHERE request_id = ? AND status = 'approved' AND reviewed_at = ?\n\t\t\t\t\t\t)`)\n\t\t\t\t\t\t\t.bind(\n\t\t\t\t\t\t\t\tbootstrapGrantId,\n\t\t\t\t\t\t\t\tbootstrapGrantInput.groupId,\n\t\t\t\t\t\t\t\tbootstrapGrantInput.seedDeviceId,\n\t\t\t\t\t\t\t\tbootstrapGrantInput.workerDeviceId,\n\t\t\t\t\t\t\t\tbootstrapGrantInput.expiresAt,\n\t\t\t\t\t\t\t\tcreatedAt,\n\t\t\t\t\t\t\t\tbootstrapGrantInput.createdBy ?? null,\n\t\t\t\t\t\t\t\t_opts.requestId,\n\t\t\t\t\t\t\t\treviewedAt,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait this.db.batch(statements);\n\t\t} else {\n\t\t\tlet createdGrantId: string | null = null;\n\t\t\tif (_opts.approved) {\n\t\t\t\tawait this.enrollDevice(row.group_id, {\n\t\t\t\t\tdeviceId: row.device_id,\n\t\t\t\t\tfingerprint: row.fingerprint,\n\t\t\t\t\tpublicKey: row.public_key,\n\t\t\t\t\tdisplayName: (row.display_name ?? \"\").trim() || null,\n\t\t\t\t});\n\t\t\t\tif (bootstrapGrantInput) {\n\t\t\t\t\tconst grant = await this.createBootstrapGrant(bootstrapGrantInput);\n\t\t\t\t\tcreatedGrantId = grant.grant_id;\n\t\t\t\t\tbootstrapGrantId = grant.grant_id;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst changes = await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`UPDATE coordinator_join_requests\n\t\t\t\t\t\t SET status = ?, reviewed_at = ?, reviewed_by = ?\n\t\t\t\t\t\t WHERE request_id = ? AND status = 'pending'`)\n\t\t\t\t\t.bind(nextStatus, reviewedAt, _opts.reviewedBy ?? null, _opts.requestId),\n\t\t\t);\n\t\t\tif (changes === 0) {\n\t\t\t\tif (createdGrantId) {\n\t\t\t\t\tawait this.revokeBootstrapGrant(createdGrantId);\n\t\t\t\t}\n\t\t\t\tconst latest = await firstRow<CoordinatorJoinRequestReviewResult>(\n\t\t\t\t\tthis.db\n\t\t\t\t\t\t.prepare(`SELECT request_id, group_id, device_id, public_key, fingerprint, display_name, token, status, created_at, reviewed_at, reviewed_by\n\t\t\t\t\t\t\t FROM coordinator_join_requests WHERE request_id = ?`)\n\t\t\t\t\t\t.bind(_opts.requestId),\n\t\t\t\t);\n\t\t\t\treturn latest\n\t\t\t\t\t? { ...rowToRecord<CoordinatorJoinRequestReviewResult>(latest), _no_transition: true }\n\t\t\t\t\t: null;\n\t\t\t}\n\t\t}\n\t\tconst updated = await firstRow<CoordinatorJoinRequestReviewResult>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, device_id, public_key, fingerprint, display_name, token, status, created_at, reviewed_at, reviewed_by\n\t\t\t\t\t FROM coordinator_join_requests WHERE request_id = ?`)\n\t\t\t\t.bind(_opts.requestId),\n\t\t);\n\t\tif (!updated) return null;\n\t\tconst bootstrapGrant = bootstrapGrantInput\n\t\t\t? bootstrapGrantId\n\t\t\t\t? await this.getBootstrapGrant(bootstrapGrantId)\n\t\t\t\t: await this.createBootstrapGrant(bootstrapGrantInput)\n\t\t\t: null;\n\t\treturn {\n\t\t\t...rowToRecord<CoordinatorJoinRequestReviewResult>(updated),\n\t\t\tbootstrap_grant: bootstrapGrant,\n\t\t};\n\t}\n\n\tasync createReciprocalApproval(\n\t\topts: CoordinatorCreateReciprocalApprovalInput,\n\t): Promise<CoordinatorReciprocalApproval> {\n\t\tconst groupId = opts.groupId.trim();\n\t\tconst requestingDeviceId = opts.requestingDeviceId.trim();\n\t\tconst requestedDeviceId = opts.requestedDeviceId.trim();\n\t\tif (!groupId || !requestingDeviceId || !requestedDeviceId) {\n\t\t\tthrow new Error(\"groupId, requestingDeviceId, and requestedDeviceId are required.\");\n\t\t}\n\t\tif (requestingDeviceId === requestedDeviceId) {\n\t\t\tthrow new Error(\"requesting and requested device ids must differ.\");\n\t\t}\n\t\tconst pendingPair = reciprocalPendingPair(requestingDeviceId, requestedDeviceId);\n\t\tconst existing = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t FROM coordinator_reciprocal_approvals\n\t\t\t\t\t WHERE group_id = ? AND requesting_device_id = ? AND requested_device_id = ? AND status = 'pending'\n\t\t\t\t\t ORDER BY created_at DESC LIMIT 1`)\n\t\t\t\t.bind(groupId, requestingDeviceId, requestedDeviceId),\n\t\t);\n\t\tif (existing) return rowToRecord<CoordinatorReciprocalApproval>(existing);\n\t\tconst reverse = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t FROM coordinator_reciprocal_approvals\n\t\t\t\t\t WHERE group_id = ? AND requesting_device_id = ? AND requested_device_id = ? AND status = 'pending'\n\t\t\t\t\t ORDER BY created_at DESC LIMIT 1`)\n\t\t\t\t.bind(groupId, requestedDeviceId, requestingDeviceId),\n\t\t);\n\t\tif (reverse) {\n\t\t\tconst resolvedAt = nowISO();\n\t\t\tawait this.db\n\t\t\t\t.prepare(\n\t\t\t\t\t`UPDATE coordinator_reciprocal_approvals SET status = 'completed', resolved_at = ? WHERE request_id = ?`,\n\t\t\t\t)\n\t\t\t\t.bind(resolvedAt, reverse.request_id)\n\t\t\t\t.run();\n\t\t\tconst completed = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t\t FROM coordinator_reciprocal_approvals WHERE request_id = ?`)\n\t\t\t\t\t.bind(reverse.request_id),\n\t\t\t);\n\t\t\treturn rowToRecord<CoordinatorReciprocalApproval>(completed);\n\t\t}\n\t\tconst requestId = tokenUrlSafe(12);\n\t\tconst createdAt = nowISO();\n\t\ttry {\n\t\t\tawait this.db\n\t\t\t\t.prepare(`INSERT INTO coordinator_reciprocal_approvals(\n\t\t\t\t\t\trequest_id,\n\t\t\t\t\t\tgroup_id,\n\t\t\t\t\t\trequesting_device_id,\n\t\t\t\t\t\trequested_device_id,\n\t\t\t\t\t\tpending_pair_low_device_id,\n\t\t\t\t\t\tpending_pair_high_device_id,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\tcreated_at,\n\t\t\t\t\t\tresolved_at\n\t\t\t\t\t) VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, NULL)`)\n\t\t\t\t.bind(\n\t\t\t\t\trequestId,\n\t\t\t\t\tgroupId,\n\t\t\t\t\trequestingDeviceId,\n\t\t\t\t\trequestedDeviceId,\n\t\t\t\t\tpendingPair.low,\n\t\t\t\t\tpendingPair.high,\n\t\t\t\t\tcreatedAt,\n\t\t\t\t)\n\t\t\t\t.run();\n\t\t} catch (error) {\n\t\t\tif (!isUniqueConstraintError(error)) throw error;\n\t\t\tconst sameDirection = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t\t FROM coordinator_reciprocal_approvals\n\t\t\t\t\t\t WHERE group_id = ? AND requesting_device_id = ? AND requested_device_id = ? AND status = 'pending'\n\t\t\t\t\t\t ORDER BY created_at DESC LIMIT 1`)\n\t\t\t\t\t.bind(groupId, requestingDeviceId, requestedDeviceId),\n\t\t\t);\n\t\t\tif (sameDirection) return rowToRecord<CoordinatorReciprocalApproval>(sameDirection);\n\t\t\tconst reverseAfterConflict = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t\t FROM coordinator_reciprocal_approvals\n\t\t\t\t\t\t WHERE group_id = ? AND requesting_device_id = ? AND requested_device_id = ? AND status = 'pending'\n\t\t\t\t\t\t ORDER BY created_at DESC LIMIT 1`)\n\t\t\t\t\t.bind(groupId, requestedDeviceId, requestingDeviceId),\n\t\t\t);\n\t\t\tif (reverseAfterConflict) {\n\t\t\t\tconst resolvedAt = nowISO();\n\t\t\t\tawait this.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`UPDATE coordinator_reciprocal_approvals SET status = 'completed', resolved_at = ? WHERE request_id = ?`,\n\t\t\t\t\t)\n\t\t\t\t\t.bind(resolvedAt, reverseAfterConflict.request_id)\n\t\t\t\t\t.run();\n\t\t\t\tconst completed = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\t\t\tthis.db\n\t\t\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t\t\t FROM coordinator_reciprocal_approvals WHERE request_id = ?`)\n\t\t\t\t\t\t.bind(reverseAfterConflict.request_id),\n\t\t\t\t);\n\t\t\t\treturn rowToRecord<CoordinatorReciprocalApproval>(completed);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tconst created = await firstRow<CoordinatorReciprocalApproval>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t FROM coordinator_reciprocal_approvals WHERE request_id = ?`)\n\t\t\t\t.bind(requestId),\n\t\t);\n\t\treturn rowToRecord<CoordinatorReciprocalApproval>(created);\n\t}\n\n\tasync createBootstrapGrant(\n\t\topts: CoordinatorCreateBootstrapGrantInput,\n\t): Promise<CoordinatorBootstrapGrant> {\n\t\tconst normalized = requireTrimmedBootstrapGrantInput(opts);\n\t\tconst grantId = tokenUrlSafe(12);\n\t\tconst createdAt = nowISO();\n\t\tawait this.db\n\t\t\t.prepare(`INSERT INTO coordinator_bootstrap_grants(\n\t\t\t\tgrant_id, group_id, seed_device_id, worker_device_id, expires_at, created_at, created_by, revoked_at\n\t\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, NULL)`)\n\t\t\t.bind(\n\t\t\t\tgrantId,\n\t\t\t\tnormalized.groupId,\n\t\t\t\tnormalized.seedDeviceId,\n\t\t\t\tnormalized.workerDeviceId,\n\t\t\t\tnormalized.expiresAt,\n\t\t\t\tcreatedAt,\n\t\t\t\tnormalized.createdBy,\n\t\t\t)\n\t\t\t.run();\n\t\tconst row = await firstRow<CoordinatorBootstrapGrant>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT grant_id, group_id, seed_device_id, worker_device_id, expires_at, created_at, created_by, revoked_at\n\t\t\t\t\t FROM coordinator_bootstrap_grants WHERE grant_id = ?`)\n\t\t\t\t.bind(grantId),\n\t\t);\n\t\treturn rowToRecord<CoordinatorBootstrapGrant>(row);\n\t}\n\n\tasync getBootstrapGrant(grantId: string): Promise<CoordinatorBootstrapGrant | null> {\n\t\tconst row = await firstRow<CoordinatorBootstrapGrant>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT grant_id, group_id, seed_device_id, worker_device_id, expires_at, created_at, created_by, revoked_at\n\t\t\t\t\t FROM coordinator_bootstrap_grants WHERE grant_id = ?`)\n\t\t\t\t.bind(grantId),\n\t\t);\n\t\treturn row ? rowToRecord<CoordinatorBootstrapGrant>(row) : null;\n\t}\n\n\tasync listBootstrapGrants(groupId: string): Promise<CoordinatorBootstrapGrant[]> {\n\t\treturn (\n\t\t\tawait allRows<CoordinatorBootstrapGrant>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT grant_id, group_id, seed_device_id, worker_device_id, expires_at, created_at, created_by, revoked_at\n\t\t\t\t\t\t FROM coordinator_bootstrap_grants WHERE group_id = ?\n\t\t\t\t\t\t ORDER BY created_at DESC, grant_id DESC`)\n\t\t\t\t\t.bind(groupId),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorBootstrapGrant>(row));\n\t}\n\n\tasync revokeBootstrapGrant(grantId: string, revokedAt = nowISO()): Promise<boolean> {\n\t\treturn (\n\t\t\t(await runChanges(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`UPDATE coordinator_bootstrap_grants\n\t\t\t\t\t\t SET revoked_at = COALESCE(revoked_at, ?)\n\t\t\t\t\t\t WHERE grant_id = ?`)\n\t\t\t\t\t.bind(revokedAt, grantId),\n\t\t\t)) > 0\n\t\t);\n\t}\n\n\tasync listReciprocalApprovals(\n\t\topts: CoordinatorListReciprocalApprovalsInput,\n\t): Promise<CoordinatorReciprocalApproval[]> {\n\t\tconst directionColumn =\n\t\t\topts.direction === \"incoming\" ? \"requested_device_id\" : \"requesting_device_id\";\n\t\tconst status = opts.status?.trim() || \"pending\";\n\t\treturn (\n\t\t\tawait allRows<CoordinatorReciprocalApproval>(\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`SELECT request_id, group_id, requesting_device_id, requested_device_id, status, created_at, resolved_at\n\t\t\t\t\t\t FROM coordinator_reciprocal_approvals\n\t\t\t\t\t\t WHERE group_id = ? AND ${directionColumn} = ? AND status = ?\n\t\t\t\t\t\t ORDER BY created_at ASC, request_id ASC`)\n\t\t\t\t\t.bind(opts.groupId, opts.deviceId, status),\n\t\t\t)\n\t\t).map((row) => rowToRecord<CoordinatorReciprocalApproval>(row));\n\t}\n\n\tasync upsertPresence(_opts: CoordinatorUpsertPresenceInput): Promise<CoordinatorPresenceRecord> {\n\t\tconst now = new Date();\n\t\tconst expiresAt = new Date(now.getTime() + _opts.ttlS * 1000).toISOString();\n\t\tconst normalized = mergeAddresses([], _opts.addresses);\n\t\tawait this.db\n\t\t\t.prepare(`INSERT INTO presence_records(group_id, device_id, addresses_json, last_seen_at, expires_at, capabilities_json)\n\t\t\t\t VALUES (?, ?, ?, ?, ?, ?)\n\t\t\t\t ON CONFLICT(group_id, device_id) DO UPDATE SET\n\t\t\t\t\taddresses_json = excluded.addresses_json,\n\t\t\t\t\tlast_seen_at = excluded.last_seen_at,\n\t\t\t\t\texpires_at = excluded.expires_at,\n\t\t\t\t\tcapabilities_json = excluded.capabilities_json`)\n\t\t\t.bind(\n\t\t\t\t_opts.groupId,\n\t\t\t\t_opts.deviceId,\n\t\t\t\tJSON.stringify(normalized),\n\t\t\t\tnow.toISOString(),\n\t\t\t\texpiresAt,\n\t\t\t\tJSON.stringify(_opts.capabilities ?? {}),\n\t\t\t)\n\t\t\t.run();\n\t\treturn {\n\t\t\tgroup_id: _opts.groupId,\n\t\t\tdevice_id: _opts.deviceId,\n\t\t\taddresses: normalized,\n\t\t\texpires_at: expiresAt,\n\t\t};\n\t}\n\n\tasync listGroupPeers(\n\t\t_groupId: string,\n\t\t_requestingDeviceId: string,\n\t): Promise<CoordinatorPeerRecord[]> {\n\t\tconst now = nowISO();\n\t\tconst rows = await allRows<Record<string, unknown>>(\n\t\t\tthis.db\n\t\t\t\t.prepare(`SELECT enrolled_devices.device_id, enrolled_devices.public_key, enrolled_devices.fingerprint, enrolled_devices.display_name,\n\t\t\t\t\t\tpresence_records.addresses_json, presence_records.last_seen_at, presence_records.expires_at,\n\t\t\t\t\t\tpresence_records.capabilities_json\n\t\t\t\t\t FROM enrolled_devices\n\t\t\t\t\t LEFT JOIN presence_records\n\t\t\t\t\t ON presence_records.group_id = enrolled_devices.group_id\n\t\t\t\t\t AND presence_records.device_id = enrolled_devices.device_id\n\t\t\t\t\t WHERE enrolled_devices.group_id = ?\n\t\t\t\t\t AND enrolled_devices.enabled = 1\n\t\t\t\t\t AND enrolled_devices.device_id != ?\n\t\t\t\t\t ORDER BY enrolled_devices.device_id ASC`)\n\t\t\t\t.bind(_groupId, _requestingDeviceId),\n\t\t);\n\t\treturn rows.map((row) => {\n\t\t\tconst expiresRaw = String(row.expires_at ?? \"\").trim();\n\t\t\tlet stale = true;\n\t\t\tif (expiresRaw) {\n\t\t\t\tconst expiresAt = new Date(expiresRaw);\n\t\t\t\tstale = Number.isNaN(expiresAt.getTime()) || expiresAt.toISOString() <= now;\n\t\t\t}\n\t\t\tconst addresses = stale\n\t\t\t\t? []\n\t\t\t\t: mergeAddresses([], JSON.parse(String(row.addresses_json ?? \"[]\")) as string[]);\n\t\t\treturn {\n\t\t\t\tdevice_id: String(row.device_id ?? \"\"),\n\t\t\t\tpublic_key: String(row.public_key ?? \"\"),\n\t\t\t\tfingerprint: String(row.fingerprint ?? \"\"),\n\t\t\t\tdisplay_name: (row.display_name as string | null) ?? null,\n\t\t\t\taddresses,\n\t\t\t\tlast_seen_at: (row.last_seen_at as string | null) ?? null,\n\t\t\t\texpires_at: (row.expires_at as string | null) ?? null,\n\t\t\t\tstale,\n\t\t\t\tcapabilities: JSON.parse(String(row.capabilities_json ?? \"{}\")) as Record<string, unknown>,\n\t\t\t} satisfies CoordinatorPeerRecord;\n\t\t});\n\t}\n}\n","import {\n\ttype CoordinatorRequestVerifier,\n\ttype CoordinatorRuntimeDeps,\n\tcreateCoordinatorApp,\n} from \"./coordinator-api.js\";\nimport { D1CoordinatorStore, type D1DatabaseLike } from \"./d1-coordinator-store.js\";\n\nexport interface CreateD1CoordinatorAppOptions {\n\tdb: D1DatabaseLike;\n\tadminSecret?: string | null;\n\tnow?: () => string;\n\trequestVerifier: CoordinatorRequestVerifier;\n}\n\nexport function createD1CoordinatorApp(\n\topts: CreateD1CoordinatorAppOptions,\n): ReturnType<typeof createCoordinatorApp> {\n\tconst runtime: CoordinatorRuntimeDeps = {\n\t\tadminSecret: () => String(opts.adminSecret ?? \"\").trim() || null,\n\t\tnow: opts.now ?? (() => new Date().toISOString()),\n\t};\n\treturn createCoordinatorApp({\n\t\tstoreFactory: () => new D1CoordinatorStore(opts.db),\n\t\truntime,\n\t\trequestVerifier: opts.requestVerifier,\n\t});\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Database } from "../db.js";
|
|
2
|
+
import { ObserverClient } from "../observer-client.js";
|
|
3
|
+
type StructuredBackfillObserver = Pick<ObserverClient, "observe" | "observeStructuredJson" | "getStatus">;
|
|
4
|
+
export interface AIBackfillStructuredContentResult {
|
|
5
|
+
checked: number;
|
|
6
|
+
updated: number;
|
|
7
|
+
skipped: number;
|
|
8
|
+
failed: number;
|
|
9
|
+
samples?: Array<{
|
|
10
|
+
id: number;
|
|
11
|
+
kind: string;
|
|
12
|
+
title: string;
|
|
13
|
+
narrative: string | null;
|
|
14
|
+
facts: string[];
|
|
15
|
+
concepts: string[];
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
export interface AIBackfillStructuredContentOptions {
|
|
19
|
+
limit?: number | null;
|
|
20
|
+
kinds?: string[] | null;
|
|
21
|
+
dryRun?: boolean;
|
|
22
|
+
overwrite?: boolean;
|
|
23
|
+
observer?: StructuredBackfillObserver;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* AI-powered backfill for older non-session-summary memories that still lack
|
|
27
|
+
* structured content (`narrative`, `facts`, `concepts`). Uses GPT-5.4 via the
|
|
28
|
+
* existing ObserverClient/OpenAI integration.
|
|
29
|
+
*/
|
|
30
|
+
export declare function aiBackfillStructuredContent(db: Database, opts?: AIBackfillStructuredContentOptions): Promise<AIBackfillStructuredContentResult>;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=ai-structured.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-structured.d.ts","sourceRoot":"","sources":["../../src/maintenance/ai-structured.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAOzC,OAAO,EAAsB,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAqC3E,KAAK,0BAA0B,GAAG,IAAI,CACrC,cAAc,EACd,SAAS,GAAG,uBAAuB,GAAG,WAAW,CACjD,CAAC;AAEF,MAAM,WAAW,iCAAiC;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,KAAK,CAAC;QACf,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC,CAAC;CACH;AAED,MAAM,WAAW,kCAAkC;IAClD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,0BAA0B,CAAC;CACtC;AAyLD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAChD,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE,kCAAuC,GAC3C,OAAO,CAAC,iCAAiC,CAAC,CAiK5C"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Database } from "../db.js";
|
|
2
|
+
export interface BackfillNarrativeResult {
|
|
3
|
+
checked: number;
|
|
4
|
+
updated: number;
|
|
5
|
+
skipped: number;
|
|
6
|
+
}
|
|
7
|
+
export interface BackfillNarrativeOptions {
|
|
8
|
+
limit?: number | null;
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Backfill narrative for session_summary memories that have structured
|
|
13
|
+
* body_text with `## Completed` / `## Learned` sections but no narrative.
|
|
14
|
+
*
|
|
15
|
+
* Only touches session_summary kind. Does not overwrite existing narratives.
|
|
16
|
+
*/
|
|
17
|
+
export declare function backfillNarrativeFromBody(db: Database, opts?: BackfillNarrativeOptions): BackfillNarrativeResult;
|
|
18
|
+
//# sourceMappingURL=backfill-narrative.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backfill-narrative.d.ts","sourceRoot":"","sources":["../../src/maintenance/backfill-narrative.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,WAAW,uBAAuB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACxC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACxC,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE,wBAA6B,GACjC,uBAAuB,CA8CzB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Database } from "../db.js";
|
|
2
|
+
export interface BackfillTagsTextOptions {
|
|
3
|
+
limit?: number | null;
|
|
4
|
+
since?: string | null;
|
|
5
|
+
project?: string | null;
|
|
6
|
+
activeOnly?: boolean;
|
|
7
|
+
dryRun?: boolean;
|
|
8
|
+
memoryIds?: number[] | null;
|
|
9
|
+
}
|
|
10
|
+
export interface BackfillTagsTextResult {
|
|
11
|
+
checked: number;
|
|
12
|
+
updated: number;
|
|
13
|
+
skipped: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Populate memory_items.tags_text for rows where it is empty.
|
|
17
|
+
*/
|
|
18
|
+
export declare function backfillTagsText(db: Database, opts?: BackfillTagsTextOptions): BackfillTagsTextResult;
|
|
19
|
+
//# sourceMappingURL=backfill-tags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backfill-tags.d.ts","sourceRoot":"","sources":["../../src/maintenance/backfill-tags.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAIzC,MAAM,WAAW,uBAAuB;IACvC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,sBAAsB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC/B,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE,uBAA4B,GAChC,sBAAsB,CAwFxB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Database } from "../db.js";
|
|
2
|
+
export interface BackfillDedupKeysResult {
|
|
3
|
+
checked: number;
|
|
4
|
+
updated: number;
|
|
5
|
+
skipped: number;
|
|
6
|
+
}
|
|
7
|
+
export interface BackfillDedupKeysPlan extends BackfillDedupKeysResult {
|
|
8
|
+
backfillable: number;
|
|
9
|
+
updates: Array<{
|
|
10
|
+
id: number;
|
|
11
|
+
dedupKey: string;
|
|
12
|
+
}>;
|
|
13
|
+
lastScannedId: number;
|
|
14
|
+
exhausted: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface BackfillDedupKeysOptions {
|
|
17
|
+
limit?: number | null;
|
|
18
|
+
dryRun?: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface BackfillDedupKeysPlanOptions {
|
|
21
|
+
rowLimit?: number | null;
|
|
22
|
+
updateLimit?: number | null;
|
|
23
|
+
afterId?: number | null;
|
|
24
|
+
}
|
|
25
|
+
export declare function planMemoryDedupKeys(db: Database, options?: BackfillDedupKeysPlanOptions): BackfillDedupKeysPlan;
|
|
26
|
+
export declare function applyMemoryDedupKeyUpdates(db: Database, updates: Array<{
|
|
27
|
+
id: number;
|
|
28
|
+
dedupKey: string;
|
|
29
|
+
}>): void;
|
|
30
|
+
export declare function backfillMemoryDedupKeys(db: Database, opts?: BackfillDedupKeysOptions): BackfillDedupKeysResult;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=dedup-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedup-keys.d.ts","sourceRoot":"","sources":["../../src/maintenance/dedup-keys.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,WAAW,uBAAuB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAsB,SAAQ,uBAAuB;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACxC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,UAAU,4BAA4B;IACrC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAqCD,wBAAgB,mBAAmB,CAClC,EAAE,EAAE,QAAQ,EACZ,OAAO,GAAE,4BAAiC,GACxC,qBAAqB,CAsEvB;AAED,wBAAgB,0BAA0B,CACzC,EAAE,EAAE,QAAQ,EACZ,OAAO,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,GAC9C,IAAI,CAWN;AAED,wBAAgB,uBAAuB,CACtC,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE,wBAA6B,GACjC,uBAAuB,CAMzB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Database } from "../db.js";
|
|
2
|
+
export interface DedupNearDuplicatesResult {
|
|
3
|
+
checked: number;
|
|
4
|
+
deactivated: number;
|
|
5
|
+
pairs: Array<{
|
|
6
|
+
kept_id: number;
|
|
7
|
+
deactivated_id: number;
|
|
8
|
+
title: string;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export interface DedupNearDuplicatesOptions {
|
|
12
|
+
/** Max time gap in milliseconds between duplicate candidates (default: 1 hour). */
|
|
13
|
+
windowMs?: number;
|
|
14
|
+
limit?: number | null;
|
|
15
|
+
dryRun?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Find and deactivate near-duplicate memories: cross-session pairs with
|
|
19
|
+
* identical normalized titles created within a configurable time window.
|
|
20
|
+
*
|
|
21
|
+
* Keeps the higher-confidence member (ties: most recent). Does not delete
|
|
22
|
+
* rows — only sets `active = 0`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function dedupNearDuplicateMemories(db: Database, opts?: DedupNearDuplicatesOptions): DedupNearDuplicatesResult;
|
|
25
|
+
//# sourceMappingURL=dedup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedup.d.ts","sourceRoot":"","sources":["../../src/maintenance/dedup.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,WAAW,yBAAyB;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzE;AAED,MAAM,WAAW,0BAA0B;IAC1C,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACzC,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE,0BAA+B,GACnC,yBAAyB,CA4E3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-vacuum.d.ts","sourceRoot":"","sources":["../../src/maintenance/init-vacuum.ts"],"names":[],"mappings":"AAUA,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAejF;AAED,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAMnF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Database } from "../db.js";
|
|
2
|
+
export interface DeactivateLowSignalResult {
|
|
3
|
+
checked: number;
|
|
4
|
+
deactivated: number;
|
|
5
|
+
}
|
|
6
|
+
export interface DeactivateLowSignalMemoriesOptions {
|
|
7
|
+
kinds?: string[] | null;
|
|
8
|
+
limit?: number | null;
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Deactivate low-signal observations only.
|
|
13
|
+
*/
|
|
14
|
+
export declare function deactivateLowSignalObservations(db: Database, limit?: number | null, dryRun?: boolean): DeactivateLowSignalResult;
|
|
15
|
+
/**
|
|
16
|
+
* Deactivate low-signal memories across selected kinds (does not delete rows).
|
|
17
|
+
*/
|
|
18
|
+
export declare function deactivateLowSignalMemories(db: Database, opts?: DeactivateLowSignalMemoriesOptions): DeactivateLowSignalResult;
|
|
19
|
+
//# sourceMappingURL=low-signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"low-signal.d.ts","sourceRoot":"","sources":["../../src/maintenance/low-signal.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,WAAW,yBAAyB;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kCAAkC;IAClD,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AA0BD;;GAEG;AACH,wBAAgB,+BAA+B,CAC9C,EAAE,EAAE,QAAQ,EACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,EACrB,MAAM,UAAQ,GACZ,yBAAyB,CAM3B;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAC1C,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE,kCAAuC,GAC3C,yBAAyB,CA0C3B"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MemoryRole, MemoryRoleProbeComparison, MemoryRoleProbeItem, MemoryRoleProbeResult } from "./types.js";
|
|
2
|
+
export interface InferredMemoryRole {
|
|
3
|
+
role: MemoryRole;
|
|
4
|
+
reason: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function classifyProjectQuality(project: unknown): "normal" | "empty" | "garbage_like";
|
|
7
|
+
export declare function safeParseMetadata(raw: string | null): Record<string, unknown>;
|
|
8
|
+
export declare function subtractKeyedCounts<T extends string>(baseline: Partial<Record<T, number>>, candidate: Partial<Record<T, number>>): Record<T, number>;
|
|
9
|
+
export declare function subtractBurden(baseline: MemoryRoleProbeResult["top_burden"] | null, candidate: MemoryRoleProbeResult["top_burden"] | null): MemoryRoleProbeResult["top_burden"] | null;
|
|
10
|
+
export declare function compareProbeResults(baseline: MemoryRoleProbeResult[], candidate: MemoryRoleProbeResult[]): MemoryRoleProbeComparison[];
|
|
11
|
+
export declare function scoreProbeScenario(items: MemoryRoleProbeItem[], query: string, mode: string): MemoryRoleProbeResult["scenario_score"] | undefined;
|
|
12
|
+
export declare function stableProbeItemKey(input: {
|
|
13
|
+
import_key?: string | null;
|
|
14
|
+
kind: string;
|
|
15
|
+
title: string;
|
|
16
|
+
body_text: string;
|
|
17
|
+
}): string;
|
|
18
|
+
//# sourceMappingURL=memory-role-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-role-helpers.d.ts","sourceRoot":"","sources":["../../src/maintenance/memory-role-helpers.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACX,UAAU,EACV,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACrB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,cAAc,CAO5F;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE7E;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,EACnD,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,EACpC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GACnC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAOnB;AAED,wBAAgB,cAAc,CAC7B,QAAQ,EAAE,qBAAqB,CAAC,YAAY,CAAC,GAAG,IAAI,EACpD,SAAS,EAAE,qBAAqB,CAAC,YAAY,CAAC,GAAG,IAAI,GACnD,qBAAqB,CAAC,YAAY,CAAC,GAAG,IAAI,CAO5C;AAED,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE,qBAAqB,EAAE,EACjC,SAAS,EAAE,qBAAqB,EAAE,GAChC,yBAAyB,EAAE,CAsF7B;AAED,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,mBAAmB,EAAE,EAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACV,qBAAqB,CAAC,gBAAgB,CAAC,GAAG,SAAS,CAwGrD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACzC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAIT"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MemoryRoleReport, MemoryRoleReportComparison, MemoryRoleReportComparisonOptions, MemoryRoleReportOptions } from "./types.js";
|
|
2
|
+
export declare function getMemoryRoleReport(dbPath?: string, opts?: MemoryRoleReportOptions): MemoryRoleReport;
|
|
3
|
+
export declare function compareMemoryRoleReports(baselineDbPath: string, candidateDbPath: string, opts?: MemoryRoleReportComparisonOptions): MemoryRoleReportComparison;
|
|
4
|
+
//# sourceMappingURL=memory-role-report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-role-report.d.ts","sourceRoot":"","sources":["../../src/maintenance/memory-role-report.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAIX,gBAAgB,EAChB,0BAA0B,EAC1B,iCAAiC,EACjC,uBAAuB,EACvB,MAAM,YAAY,CAAC;AAsBpB,wBAAgB,mBAAmB,CAClC,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,GAAE,uBAA4B,GAChC,gBAAgB,CAgVlB;AAED,wBAAgB,wBAAwB,CACvC,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,MAAM,EACvB,IAAI,GAAE,iCAAsC,GAC1C,0BAA0B,CAiC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrative-extract.d.ts","sourceRoot":"","sources":["../../src/maintenance/narrative-extract.ts"],"names":[],"mappings":"AAEA,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgBxE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface ReliabilityMetrics {
|
|
2
|
+
counts: {
|
|
3
|
+
inserted_events: number;
|
|
4
|
+
dropped_events: number;
|
|
5
|
+
started_batches: number;
|
|
6
|
+
running_batches: number;
|
|
7
|
+
completed_batches: number;
|
|
8
|
+
errored_batches: number;
|
|
9
|
+
terminal_batches: number;
|
|
10
|
+
sessions_with_events: number;
|
|
11
|
+
sessions_with_started_at: number;
|
|
12
|
+
retry_depth_max: number;
|
|
13
|
+
};
|
|
14
|
+
rates: {
|
|
15
|
+
flush_success_rate: number;
|
|
16
|
+
dropped_event_rate: number;
|
|
17
|
+
session_boundary_accuracy: number;
|
|
18
|
+
};
|
|
19
|
+
window_hours: number | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function getReliabilityMetrics(dbPath?: string, windowHours?: number | null): ReliabilityMetrics;
|
|
22
|
+
export interface GateResult {
|
|
23
|
+
passed: boolean;
|
|
24
|
+
failures: string[];
|
|
25
|
+
metrics: ReliabilityMetrics;
|
|
26
|
+
}
|
|
27
|
+
export declare function rawEventsGate(dbPath?: string, opts?: {
|
|
28
|
+
minFlushSuccessRate?: number;
|
|
29
|
+
maxDroppedEventRate?: number;
|
|
30
|
+
minSessionBoundaryAccuracy?: number;
|
|
31
|
+
windowHours?: number;
|
|
32
|
+
}): GateResult;
|
|
33
|
+
//# sourceMappingURL=reliability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reliability.d.ts","sourceRoot":"","sources":["../../src/maintenance/reliability.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE;QACP,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;QACxB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,eAAe,EAAE,MAAM,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,wBAAwB,EAAE,MAAM,CAAC;QACjC,eAAe,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,KAAK,EAAE;QACN,kBAAkB,EAAE,MAAM,CAAC;QAC3B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,yBAAyB,EAAE,MAAM,CAAC;KAClC,CAAC;IACF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAgB,qBAAqB,CACpC,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GACzB,kBAAkB,CAoLpB;AAED,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,kBAAkB,CAAC;CAC5B;AAED,wBAAgB,aAAa,CAC5B,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IACN,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,GACC,UAAU,CA0BZ"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Database } from "../db.js";
|
|
2
|
+
import type { RawEventRelinkApplyOptions, RawEventRelinkApplyResult, RawEventRelinkPlan, RawEventRelinkPlanOptions, RawEventRelinkReport, RawEventRelinkReportOptions } from "./types.js";
|
|
3
|
+
export declare function applyRawEventRelinkPlanWithDb(db: Database, opts?: RawEventRelinkApplyOptions): RawEventRelinkApplyResult;
|
|
4
|
+
export declare function getRawEventRelinkReport(dbPath?: string, opts?: RawEventRelinkReportOptions): RawEventRelinkReport;
|
|
5
|
+
export declare function getRawEventRelinkPlan(dbPath?: string, opts?: RawEventRelinkPlanOptions): RawEventRelinkPlan;
|
|
6
|
+
export declare function applyRawEventRelinkPlan(dbPath?: string, opts?: RawEventRelinkApplyOptions): RawEventRelinkApplyResult;
|
|
7
|
+
//# sourceMappingURL=relink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relink.d.ts","sourceRoot":"","sources":["../../src/maintenance/relink.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,KAAK,EAEX,0BAA0B,EAC1B,yBAAyB,EAEzB,kBAAkB,EAClB,yBAAyB,EACzB,oBAAoB,EACpB,2BAA2B,EAC3B,MAAM,YAAY,CAAC;AA4MpB,wBAAgB,6BAA6B,CAC5C,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE,0BAA+B,GACnC,yBAAyB,CAuF3B;AAED,wBAAgB,uBAAuB,CACtC,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,GAAE,2BAAgC,GACpC,oBAAoB,CAEtB;AAED,wBAAgB,qBAAqB,CACpC,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,GAAE,yBAA8B,GAClC,kBAAkB,CAiEpB;AAED,wBAAgB,uBAAuB,CACtC,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,GAAE,0BAA+B,GACnC,yBAAyB,CAE3B"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RawEventStatusResult } from "./types.js";
|
|
2
|
+
export declare function getRawEventStatus(dbPath?: string, limit?: number): RawEventStatusResult;
|
|
3
|
+
export declare function retryRawEventFailures(dbPath?: string, limit?: number): {
|
|
4
|
+
retried: number;
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/maintenance/status.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,oBAAoB,CA0FnF;AAED,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CA0CtF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function normalizeTag(value: string): string;
|
|
2
|
+
export declare function fileTags(pathValue: string): string[];
|
|
3
|
+
export declare function parseJsonStringList(value: string | null): string[];
|
|
4
|
+
export declare function deriveTags(input: {
|
|
5
|
+
kind: string;
|
|
6
|
+
title: string;
|
|
7
|
+
concepts: string[];
|
|
8
|
+
filesRead: string[];
|
|
9
|
+
filesModified: string[];
|
|
10
|
+
}): string[];
|
|
11
|
+
//# sourceMappingURL=tag-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag-helpers.d.ts","sourceRoot":"","sources":["../../src/maintenance/tag-helpers.ts"],"names":[],"mappings":"AAEA,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQlD;AAED,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBpD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAWlE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;CACxB,GAAG,MAAM,EAAE,CA+BX"}
|