@totemsdk/omnia-host 0.9.0 → 0.11.0
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/LICENSE +202 -21
- package/NOTICE +60 -0
- package/README.md +44 -0
- package/dist/api/jsonrpc.d.ts +12 -0
- package/dist/api/jsonrpc.js +100 -1
- package/dist/api/methods.d.ts +135 -3
- package/dist/api/methods.js +1986 -45
- package/dist/chain/totem-node-adapter.js +1 -1
- package/dist/cli.js +67 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +33 -1
- package/dist/identity.js +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +20 -1
- package/dist/lifecycle.d.ts +22 -0
- package/dist/lifecycle.js +422 -5
- package/dist/observe/bus.d.ts +13 -0
- package/dist/observe/bus.js +29 -0
- package/dist/observe/emitter.d.ts +61 -0
- package/dist/observe/emitter.js +133 -0
- package/dist/observe/index.d.ts +5 -0
- package/dist/observe/index.js +21 -0
- package/dist/observe/snapshot.d.ts +38 -0
- package/dist/observe/snapshot.js +101 -0
- package/dist/observe/store.d.ts +31 -0
- package/dist/observe/store.js +82 -0
- package/dist/observe/types.d.ts +106 -0
- package/dist/observe/types.js +14 -0
- package/dist/pairing.d.ts +109 -0
- package/dist/pairing.js +231 -0
- package/dist/reply-cache.d.ts +21 -0
- package/dist/reply-cache.js +62 -0
- package/dist/signer-port.d.ts +27 -0
- package/dist/signer-port.js +79 -0
- package/dist/signing.d.ts +11 -0
- package/dist/signing.js +27 -0
- package/dist/stores/operations.d.ts +3 -2
- package/dist/stores/operations.js +96 -83
- package/dist/stores/sqlite-store.d.ts +8 -5
- package/dist/stores/sqlite-store.js +45 -42
- package/package.json +16 -14
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ObservationEmitter = void 0;
|
|
4
|
+
exports.hashTransition = hashTransition;
|
|
5
|
+
exports.channelTransitionHash = channelTransitionHash;
|
|
6
|
+
exports.hashString = hashString;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const SECRET_KEY = /^(seed|seedphrase|mnemonic|privatekey|secretkey|preimage|witness|witnesshex|fundingwitness|keyfile|passphrase|password|token|signature)$/i;
|
|
9
|
+
const MAX_STRING = 512;
|
|
10
|
+
const MAX_ARRAY = 32;
|
|
11
|
+
/** Stable hash for transition identity (never includes secrets). */
|
|
12
|
+
function hashTransition(parts) {
|
|
13
|
+
const hash = (0, node_crypto_1.createHash)("sha256");
|
|
14
|
+
for (const part of parts)
|
|
15
|
+
hash.update(String(part ?? ""), "utf8").update("\u0000");
|
|
16
|
+
return "sha256:" + hash.digest("hex");
|
|
17
|
+
}
|
|
18
|
+
/** Hash a channel's signed commitment for cross-node correlation. */
|
|
19
|
+
function channelTransitionHash(channel) {
|
|
20
|
+
const state = channel.latestState;
|
|
21
|
+
return hashTransition([
|
|
22
|
+
"channel",
|
|
23
|
+
channel.channelId,
|
|
24
|
+
channel.fundingCoinId,
|
|
25
|
+
channel.currentSequence,
|
|
26
|
+
state?.sequence,
|
|
27
|
+
state?.transactionHex ? hashString(state.transactionHex) : undefined,
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
30
|
+
function hashString(value) {
|
|
31
|
+
return (0, node_crypto_1.createHash)("sha256").update(value, "utf8").digest("hex");
|
|
32
|
+
}
|
|
33
|
+
function redact(value, depth = 0) {
|
|
34
|
+
if (value === null || value === undefined)
|
|
35
|
+
return value;
|
|
36
|
+
if (typeof value === "bigint")
|
|
37
|
+
return value.toString();
|
|
38
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
39
|
+
return value;
|
|
40
|
+
if (typeof value === "string") {
|
|
41
|
+
if (value.length <= MAX_STRING)
|
|
42
|
+
return value;
|
|
43
|
+
return `${value.slice(0, MAX_STRING)}…[sha256:${hashString(value).slice(0, 16)}]`;
|
|
44
|
+
}
|
|
45
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
|
|
46
|
+
return { bytes: value.length, sha256: hashString(Buffer.from(value).toString("hex")).slice(0, 16) };
|
|
47
|
+
}
|
|
48
|
+
if (depth > 4)
|
|
49
|
+
return "[depth-limit]";
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
const head = value.slice(0, MAX_ARRAY).map((v) => redact(v, depth + 1));
|
|
52
|
+
return value.length > MAX_ARRAY ? [...head, `[${value.length - MAX_ARRAY} more]`] : head;
|
|
53
|
+
}
|
|
54
|
+
if (typeof value === "object") {
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
57
|
+
if (SECRET_KEY.test(key)) {
|
|
58
|
+
out[key] = "[redacted]";
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
out[key] = redact(nested, depth + 1);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
return String(value);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Builds, redacts and persists observation events, then fans them out live.
|
|
69
|
+
*
|
|
70
|
+
* `emit()` never throws into a mutation: persistence/export failures are
|
|
71
|
+
* reported out-of-band so a payment can never be blocked by the observer.
|
|
72
|
+
*/
|
|
73
|
+
class ObservationEmitter {
|
|
74
|
+
store;
|
|
75
|
+
bus;
|
|
76
|
+
networkId;
|
|
77
|
+
sourceNodeId;
|
|
78
|
+
streamId;
|
|
79
|
+
simulated;
|
|
80
|
+
onError;
|
|
81
|
+
constructor(config) {
|
|
82
|
+
this.store = config.store;
|
|
83
|
+
this.bus = config.bus;
|
|
84
|
+
this.networkId = config.networkId;
|
|
85
|
+
this.sourceNodeId = config.sourceNodeId;
|
|
86
|
+
this.streamId = config.streamId ?? `${config.sourceNodeId}:${config.networkId}`;
|
|
87
|
+
this.simulated = config.simulated ?? false;
|
|
88
|
+
this.onError = config.onError ?? ((err) => console.error("[omnia-observe] emit failed:", err));
|
|
89
|
+
}
|
|
90
|
+
async emit(input) {
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const record = {
|
|
93
|
+
schemaVersion: 1,
|
|
94
|
+
networkId: this.networkId,
|
|
95
|
+
sourceNodeId: this.sourceNodeId,
|
|
96
|
+
streamId: this.streamId,
|
|
97
|
+
eventId: (0, node_crypto_1.randomUUID)(),
|
|
98
|
+
recordedAt: now,
|
|
99
|
+
occurredAt: input.occurredAt ?? now,
|
|
100
|
+
domain: input.domain,
|
|
101
|
+
kind: input.kind,
|
|
102
|
+
phase: input.phase,
|
|
103
|
+
...(input.transitionHash ? { transitionHash: input.transitionHash } : {}),
|
|
104
|
+
...(input.operationId ? { operationId: input.operationId } : {}),
|
|
105
|
+
...(input.causationId ? { causationId: input.causationId } : {}),
|
|
106
|
+
entity: input.entity,
|
|
107
|
+
...(input.parties ? { parties: input.parties } : {}),
|
|
108
|
+
...(input.token ? { token: input.token } : {}),
|
|
109
|
+
...(input.before ? { before: redact(input.before) } : {}),
|
|
110
|
+
...(input.after ? { after: redact(input.after) } : {}),
|
|
111
|
+
...(input.amounts ? { amounts: input.amounts } : {}),
|
|
112
|
+
...(input.chain ? { chain: input.chain } : {}),
|
|
113
|
+
...(input.evidence ? { evidence: input.evidence } : {}),
|
|
114
|
+
simulated: input.simulated ?? this.simulated,
|
|
115
|
+
...(input.data ? { data: redact(input.data) } : {}),
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
const { seq, createdAt } = await this.store.append(record);
|
|
119
|
+
const observation = {
|
|
120
|
+
...record,
|
|
121
|
+
sourceSequence: seq,
|
|
122
|
+
recordedAt: createdAt,
|
|
123
|
+
};
|
|
124
|
+
this.bus.publish(observation);
|
|
125
|
+
return observation;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
this.onError(error);
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.ObservationEmitter = ObservationEmitter;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types.js"), exports);
|
|
18
|
+
__exportStar(require("./bus.js"), exports);
|
|
19
|
+
__exportStar(require("./emitter.js"), exports);
|
|
20
|
+
__exportStar(require("./store.js"), exports);
|
|
21
|
+
__exportStar(require("./snapshot.js"), exports);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { OmniaChannel } from "@totemsdk/omnia";
|
|
2
|
+
import type { ChannelFactory } from "@totemsdk/omnia-factory";
|
|
3
|
+
export interface ObservationSnapshotInput {
|
|
4
|
+
networkId: string;
|
|
5
|
+
sourceNodeId: string;
|
|
6
|
+
streamId: string;
|
|
7
|
+
cursor: number;
|
|
8
|
+
channels?: Iterable<OmniaChannel>;
|
|
9
|
+
factories?: Iterable<ChannelFactory>;
|
|
10
|
+
vtxoPools?: Iterable<unknown>;
|
|
11
|
+
vtxos?: Iterable<unknown>;
|
|
12
|
+
pools?: Iterable<unknown>;
|
|
13
|
+
operations?: {
|
|
14
|
+
listByStatus?: (status: string) => unknown[];
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Snapshot the host's current state at a single journal boundary. The cursor is
|
|
19
|
+
* the journal head, so `GET /observe/events?after=<cursor>` has no gap or
|
|
20
|
+
* duplication relative to this snapshot.
|
|
21
|
+
*/
|
|
22
|
+
export interface ObservationSnapshotState {
|
|
23
|
+
channels: unknown[];
|
|
24
|
+
factories: unknown[];
|
|
25
|
+
vtxoPools: unknown[];
|
|
26
|
+
vtxos: unknown[];
|
|
27
|
+
pools: unknown[];
|
|
28
|
+
operations: unknown[];
|
|
29
|
+
}
|
|
30
|
+
export interface ObservationSnapshot {
|
|
31
|
+
networkId: string;
|
|
32
|
+
sourceNodeId: string;
|
|
33
|
+
streamId: string;
|
|
34
|
+
cursor: number;
|
|
35
|
+
generatedAt: number;
|
|
36
|
+
state: ObservationSnapshotState;
|
|
37
|
+
}
|
|
38
|
+
export declare function createObservationSnapshot(input: ObservationSnapshotInput): ObservationSnapshot;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createObservationSnapshot = createObservationSnapshot;
|
|
4
|
+
function toJson(value, depth = 0) {
|
|
5
|
+
if (value === null || value === undefined)
|
|
6
|
+
return value;
|
|
7
|
+
if (typeof value === "bigint")
|
|
8
|
+
return value.toString();
|
|
9
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "string")
|
|
10
|
+
return value;
|
|
11
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
|
|
12
|
+
return { bytes: value.length };
|
|
13
|
+
}
|
|
14
|
+
if (depth > 4)
|
|
15
|
+
return "[depth-limit]";
|
|
16
|
+
if (Array.isArray(value))
|
|
17
|
+
return value.map((v) => toJson(v, depth + 1));
|
|
18
|
+
if (value instanceof Map) {
|
|
19
|
+
return Object.fromEntries(Array.from(value.entries()).map(([k, v]) => [String(k), toJson(v, depth + 1)]));
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === "object") {
|
|
22
|
+
const out = {};
|
|
23
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
24
|
+
if (/seed|privatekey|secretkey|preimage|witness|signature/i.test(key))
|
|
25
|
+
continue;
|
|
26
|
+
out[key] = toJson(nested, depth + 1);
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
return String(value);
|
|
31
|
+
}
|
|
32
|
+
function channelView(channel) {
|
|
33
|
+
return {
|
|
34
|
+
channelId: channel.channelId,
|
|
35
|
+
status: channel.status,
|
|
36
|
+
channelType: channel.channelType,
|
|
37
|
+
tokenId: channel.tokenId,
|
|
38
|
+
tokenScale: channel.tokenScale ?? 0,
|
|
39
|
+
totalValue: channel.totalValue.toString(),
|
|
40
|
+
currentSequence: channel.currentSequence,
|
|
41
|
+
fundingCoinId: channel.fundingCoinId,
|
|
42
|
+
fundingTxId: channel.fundingTxId,
|
|
43
|
+
parties: channel.parties,
|
|
44
|
+
balances: Object.fromEntries(Object.entries(channel.balances).map(([k, v]) => [k, v.toString()])),
|
|
45
|
+
pendingHTLCs: (channel.pendingHTLCs ?? []).map((h) => toJson(h)),
|
|
46
|
+
updatedAt: channel.updatedAt,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function factoryView(factory) {
|
|
50
|
+
const f = factory;
|
|
51
|
+
const allocations = (f.allocations ?? {});
|
|
52
|
+
return {
|
|
53
|
+
factoryId: f.factoryId,
|
|
54
|
+
status: f.status,
|
|
55
|
+
tokenId: f.tokenId,
|
|
56
|
+
tokenScale: f.tokenScale ?? 0,
|
|
57
|
+
participants: f.participants,
|
|
58
|
+
allocations: Object.fromEntries(Object.entries(allocations).map(([k, v]) => [k, v.toString()])),
|
|
59
|
+
updatedAt: f.updatedAt,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function operationsView(operations) {
|
|
63
|
+
if (!operations?.listByStatus)
|
|
64
|
+
return [];
|
|
65
|
+
const statuses = ["pending", "executing", "committed", "failed"];
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const status of statuses) {
|
|
69
|
+
for (const record of operations.listByStatus(status) ?? []) {
|
|
70
|
+
const r = record;
|
|
71
|
+
if (r.operationId && seen.has(r.operationId))
|
|
72
|
+
continue;
|
|
73
|
+
if (r.operationId)
|
|
74
|
+
seen.add(r.operationId);
|
|
75
|
+
out.push({
|
|
76
|
+
operationId: r.operationId,
|
|
77
|
+
status: record.status ?? status,
|
|
78
|
+
updatedAt: record.updatedAt,
|
|
79
|
+
error: record.error,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
function createObservationSnapshot(input) {
|
|
86
|
+
return {
|
|
87
|
+
networkId: input.networkId,
|
|
88
|
+
sourceNodeId: input.sourceNodeId,
|
|
89
|
+
streamId: input.streamId,
|
|
90
|
+
cursor: input.cursor,
|
|
91
|
+
generatedAt: Date.now(),
|
|
92
|
+
state: {
|
|
93
|
+
channels: Array.from(input.channels ?? []).map(channelView),
|
|
94
|
+
factories: Array.from(input.factories ?? []).map(factoryView),
|
|
95
|
+
vtxoPools: Array.from(input.vtxoPools ?? []).map((v) => toJson(v)),
|
|
96
|
+
vtxos: Array.from(input.vtxos ?? []).map((v) => toJson(v)),
|
|
97
|
+
pools: Array.from(input.pools ?? []).map((v) => toJson(v)),
|
|
98
|
+
operations: operationsView(input.operations),
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ObservationStore, OmniaObservation } from "./types.js";
|
|
2
|
+
export interface SqliteObservationStoreOptions {
|
|
3
|
+
namespace?: string;
|
|
4
|
+
/** WAL is on by default in SqliteStore; exposed for completeness. */
|
|
5
|
+
wal?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Durable observation journal backed by `@totemsdk/storage` (RFC-007).
|
|
9
|
+
*
|
|
10
|
+
* Deliberately **not** a direct `better-sqlite3`/DuckDB write: omnia-host
|
|
11
|
+
* persists through the storage SDK so the same adapter can be swapped
|
|
12
|
+
* (file/idb/memory) and the journal gets CAS head sequencing, crash-recovery and
|
|
13
|
+
* forward-migratable records for free.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createSqliteObservationStore(dbPath: string, options?: SqliteObservationStoreOptions): Promise<ObservationStore>;
|
|
16
|
+
/** In-memory journal for tests/embedding (volatile acknowledgment). */
|
|
17
|
+
export declare function createMemoryObservationStore(options?: {
|
|
18
|
+
namespace?: string;
|
|
19
|
+
}): Promise<ObservationStore>;
|
|
20
|
+
/** No-op store for read-only/embedded deployments that opt out of observation. */
|
|
21
|
+
export declare class DisabledObservationStore implements ObservationStore {
|
|
22
|
+
append(): Promise<{
|
|
23
|
+
seq: number;
|
|
24
|
+
createdAt: number;
|
|
25
|
+
}>;
|
|
26
|
+
readSince(): Promise<OmniaObservation[]>;
|
|
27
|
+
tail(): Promise<OmniaObservation[]>;
|
|
28
|
+
head(): Promise<number>;
|
|
29
|
+
count(): Promise<number>;
|
|
30
|
+
close(): Promise<void>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DisabledObservationStore = void 0;
|
|
4
|
+
exports.createSqliteObservationStore = createSqliteObservationStore;
|
|
5
|
+
exports.createMemoryObservationStore = createMemoryObservationStore;
|
|
6
|
+
const journal_1 = require("@totemsdk/storage/journal");
|
|
7
|
+
const memory_1 = require("@totemsdk/storage/memory");
|
|
8
|
+
const sqlite_1 = require("@totemsdk/storage/sqlite");
|
|
9
|
+
const DEFAULT_NAMESPACE = "omnia:observe:v1:";
|
|
10
|
+
function materialize(seq, record) {
|
|
11
|
+
return { ...record, sourceSequence: seq };
|
|
12
|
+
}
|
|
13
|
+
function makeStore(journal, closeAdapter) {
|
|
14
|
+
return {
|
|
15
|
+
async append(record) {
|
|
16
|
+
const entry = await journal.append(record);
|
|
17
|
+
return { seq: entry.seq, createdAt: entry.createdAt };
|
|
18
|
+
},
|
|
19
|
+
async readSince(seq) {
|
|
20
|
+
const entries = await journal.readSince(Math.max(0, Math.floor(seq)));
|
|
21
|
+
return entries.map((e) => materialize(e.seq, e.record));
|
|
22
|
+
},
|
|
23
|
+
async tail(count) {
|
|
24
|
+
const entries = await journal.tail(Math.max(0, Math.floor(count)));
|
|
25
|
+
return entries.map((e) => materialize(e.seq, e.record));
|
|
26
|
+
},
|
|
27
|
+
head: () => journal.getHead(),
|
|
28
|
+
count: () => journal.count(),
|
|
29
|
+
close: closeAdapter,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Durable observation journal backed by `@totemsdk/storage` (RFC-007).
|
|
34
|
+
*
|
|
35
|
+
* Deliberately **not** a direct `better-sqlite3`/DuckDB write: omnia-host
|
|
36
|
+
* persists through the storage SDK so the same adapter can be swapped
|
|
37
|
+
* (file/idb/memory) and the journal gets CAS head sequencing, crash-recovery and
|
|
38
|
+
* forward-migratable records for free.
|
|
39
|
+
*/
|
|
40
|
+
async function createSqliteObservationStore(dbPath, options = {}) {
|
|
41
|
+
const adapter = new sqlite_1.SqliteStore(dbPath, { wal: options.wal ?? true });
|
|
42
|
+
const journal = (0, journal_1.createJournal)(adapter, {
|
|
43
|
+
namespace: options.namespace ?? DEFAULT_NAMESPACE,
|
|
44
|
+
requireAckMode: "durably-acknowledged",
|
|
45
|
+
});
|
|
46
|
+
await journal.recover();
|
|
47
|
+
return makeStore(journal, async () => {
|
|
48
|
+
await adapter.close?.();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** In-memory journal for tests/embedding (volatile acknowledgment). */
|
|
52
|
+
async function createMemoryObservationStore(options = {}) {
|
|
53
|
+
const adapter = new memory_1.MemoryStore();
|
|
54
|
+
const journal = (0, journal_1.createJournal)(adapter, {
|
|
55
|
+
namespace: options.namespace ?? DEFAULT_NAMESPACE,
|
|
56
|
+
requireAckMode: "volatile",
|
|
57
|
+
});
|
|
58
|
+
await journal.recover();
|
|
59
|
+
return makeStore(journal, async () => {
|
|
60
|
+
await adapter.close?.();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** No-op store for read-only/embedded deployments that opt out of observation. */
|
|
64
|
+
class DisabledObservationStore {
|
|
65
|
+
async append() {
|
|
66
|
+
return { seq: 0, createdAt: Date.now() };
|
|
67
|
+
}
|
|
68
|
+
async readSince() {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
async tail() {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
async head() {
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
async count() {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
async close() { }
|
|
81
|
+
}
|
|
82
|
+
exports.DisabledObservationStore = DisabledObservationStore;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Omnia observation events (RFC-0011 Amendment A / RFC-048).
|
|
3
|
+
*
|
|
4
|
+
* One event is a durable, replayable record of a single domain transition. It is
|
|
5
|
+
* intentionally richer than `AnalyticsEvent`: it carries identity, correlation,
|
|
6
|
+
* exact values, chain/evidence refs and a compact before/after diff so a
|
|
7
|
+
* consumer (the canary observer, an explorer, an agent) can reconstruct state
|
|
8
|
+
* without guessing or polling.
|
|
9
|
+
*
|
|
10
|
+
* Money is always an exact string (atoms). Signatures/seeds/preimages are never
|
|
11
|
+
* emitted — only hashes/indices.
|
|
12
|
+
*/
|
|
13
|
+
export type ObservationDomain = "node" | "channel" | "funding" | "payment" | "settlement" | "factory" | "vtxo" | "pool" | "splice" | "transport" | "operation" | "command";
|
|
14
|
+
export type ObservationPhase = "observed" | "proposed" | "accepted" | "prepared" | "executing" | "committed" | "broadcast" | "confirmed" | "closed" | "failed" | "recovered";
|
|
15
|
+
export type ObservationEntityType = "node" | "channel" | "factory" | "virtual-channel" | "vtxo-pool" | "vtxo" | "pool" | "splice" | "payment" | "operation" | "command";
|
|
16
|
+
export interface ObservationEntity {
|
|
17
|
+
type: ObservationEntityType;
|
|
18
|
+
id: string;
|
|
19
|
+
/** Funding generation for channels/factories; increments on splice/reopen. */
|
|
20
|
+
generation?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface OmniaObservation {
|
|
23
|
+
schemaVersion: 1;
|
|
24
|
+
networkId: string;
|
|
25
|
+
sourceNodeId: string;
|
|
26
|
+
streamId: string;
|
|
27
|
+
/** Journal-assigned 1-based sequence (the resumable cursor). */
|
|
28
|
+
sourceSequence: number;
|
|
29
|
+
eventId: string;
|
|
30
|
+
recordedAt: number;
|
|
31
|
+
occurredAt: number;
|
|
32
|
+
domain: ObservationDomain;
|
|
33
|
+
kind: string;
|
|
34
|
+
phase: ObservationPhase;
|
|
35
|
+
/** Protocol-level identity of the underlying signed transition, if known. */
|
|
36
|
+
transitionHash?: string;
|
|
37
|
+
/** Caller-known operation correlation (propagated where peer messages allow). */
|
|
38
|
+
operationId?: string;
|
|
39
|
+
/** The command/message that caused this transition. */
|
|
40
|
+
causationId?: string;
|
|
41
|
+
entity: ObservationEntity;
|
|
42
|
+
parties?: {
|
|
43
|
+
local?: string;
|
|
44
|
+
remote?: string;
|
|
45
|
+
};
|
|
46
|
+
token?: {
|
|
47
|
+
tokenId: string;
|
|
48
|
+
scale: number;
|
|
49
|
+
};
|
|
50
|
+
before?: Record<string, unknown>;
|
|
51
|
+
after?: Record<string, unknown>;
|
|
52
|
+
amounts?: {
|
|
53
|
+
amountAtoms?: string;
|
|
54
|
+
capacity?: string;
|
|
55
|
+
fee?: string;
|
|
56
|
+
balances?: Record<string, string>;
|
|
57
|
+
};
|
|
58
|
+
chain?: {
|
|
59
|
+
fundingCoinId?: string;
|
|
60
|
+
txpowId?: string;
|
|
61
|
+
blockHeight?: number;
|
|
62
|
+
confirmations?: number;
|
|
63
|
+
txDigest?: string;
|
|
64
|
+
};
|
|
65
|
+
evidence?: {
|
|
66
|
+
stateDigest?: string;
|
|
67
|
+
signatureHashes?: string[];
|
|
68
|
+
leaseIndices?: number[];
|
|
69
|
+
peerPublicKeyDigest?: string;
|
|
70
|
+
proofRefs?: string[];
|
|
71
|
+
manifestId?: string;
|
|
72
|
+
};
|
|
73
|
+
/** True when the transition was simulated/fixture-backed. */
|
|
74
|
+
simulated: boolean;
|
|
75
|
+
/** Free-form, already-redacted extras. */
|
|
76
|
+
data?: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
/** A record before the journal assigns its sequence. */
|
|
79
|
+
export type ObservationRecord = Omit<OmniaObservation, "sourceSequence">;
|
|
80
|
+
export interface ObserveSnapshot {
|
|
81
|
+
networkId: string;
|
|
82
|
+
sourceNodeId: string;
|
|
83
|
+
streamId: string;
|
|
84
|
+
/** Journal head at snapshot time — pair with `GET /observe/events?after=`. */
|
|
85
|
+
cursor: number;
|
|
86
|
+
generatedAt: number;
|
|
87
|
+
state: {
|
|
88
|
+
channels: unknown[];
|
|
89
|
+
factories: unknown[];
|
|
90
|
+
vtxoPools: unknown[];
|
|
91
|
+
vtxos: unknown[];
|
|
92
|
+
pools: unknown[];
|
|
93
|
+
operations: unknown[];
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export interface ObservationStore {
|
|
97
|
+
append(record: ObservationRecord): Promise<{
|
|
98
|
+
seq: number;
|
|
99
|
+
createdAt: number;
|
|
100
|
+
}>;
|
|
101
|
+
readSince(seq: number): Promise<OmniaObservation[]>;
|
|
102
|
+
tail(count: number): Promise<OmniaObservation[]>;
|
|
103
|
+
head(): Promise<number>;
|
|
104
|
+
count(): Promise<number>;
|
|
105
|
+
close(): Promise<void>;
|
|
106
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Omnia observation events (RFC-0011 Amendment A / RFC-048).
|
|
4
|
+
*
|
|
5
|
+
* One event is a durable, replayable record of a single domain transition. It is
|
|
6
|
+
* intentionally richer than `AnalyticsEvent`: it carries identity, correlation,
|
|
7
|
+
* exact values, chain/evidence refs and a compact before/after diff so a
|
|
8
|
+
* consumer (the canary observer, an explorer, an agent) can reconstruct state
|
|
9
|
+
* without guessing or polling.
|
|
10
|
+
*
|
|
11
|
+
* Money is always an exact string (atoms). Signatures/seeds/preimages are never
|
|
12
|
+
* emitted — only hashes/indices.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-062 — local node/extension pairing MVP.
|
|
3
|
+
*
|
|
4
|
+
* A loopback HTTP service issues one-time pairing codes, verifies the extension
|
|
5
|
+
* origin, binds a device key and returns a scoped bearer token plus the node
|
|
6
|
+
* identity/fingerprint and advertised capabilities (chain RPC, P2P hub, lease).
|
|
7
|
+
*/
|
|
8
|
+
import type { AddressInfo } from 'node:net';
|
|
9
|
+
export interface PairingCapabilities {
|
|
10
|
+
chain?: {
|
|
11
|
+
rpcUrl: string;
|
|
12
|
+
network: string;
|
|
13
|
+
genesisHash?: string;
|
|
14
|
+
};
|
|
15
|
+
lease?: {
|
|
16
|
+
modes: Array<'local' | 'hybrid'>;
|
|
17
|
+
onchainWatermarkUrl?: string;
|
|
18
|
+
};
|
|
19
|
+
omnia?: {
|
|
20
|
+
relayUrl?: string;
|
|
21
|
+
hostControlUrl?: string;
|
|
22
|
+
transportPubkey?: string;
|
|
23
|
+
};
|
|
24
|
+
p2p?: {
|
|
25
|
+
hubUrl: string;
|
|
26
|
+
token?: string;
|
|
27
|
+
scopes: Array<'p2p.pubsub'>;
|
|
28
|
+
topicPrefixes?: string[];
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface LinkedClient {
|
|
32
|
+
clientId: string;
|
|
33
|
+
origin: string;
|
|
34
|
+
devicePublicKey: string;
|
|
35
|
+
scopes: string[];
|
|
36
|
+
tokenHash: string;
|
|
37
|
+
createdAt: number;
|
|
38
|
+
lastUsedAt: number;
|
|
39
|
+
revokedAt?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface PairingInvitation {
|
|
42
|
+
endpoint: string;
|
|
43
|
+
code: string;
|
|
44
|
+
nodeId: string;
|
|
45
|
+
fingerprint: string;
|
|
46
|
+
network: string;
|
|
47
|
+
scopes: string[];
|
|
48
|
+
capabilities: PairingCapabilities;
|
|
49
|
+
expiresAt: number;
|
|
50
|
+
}
|
|
51
|
+
export interface PairingServiceOptions {
|
|
52
|
+
storePath: string;
|
|
53
|
+
endpoint?: string;
|
|
54
|
+
network?: string;
|
|
55
|
+
ttlMs?: number;
|
|
56
|
+
allowedOrigins?: string[];
|
|
57
|
+
scopes?: string[];
|
|
58
|
+
capabilities?: PairingCapabilities;
|
|
59
|
+
now?: () => number;
|
|
60
|
+
}
|
|
61
|
+
export interface PairingService {
|
|
62
|
+
readonly nodeId: string;
|
|
63
|
+
readonly fingerprint: string;
|
|
64
|
+
readonly network: string;
|
|
65
|
+
readonly scopes: string[];
|
|
66
|
+
readonly capabilities: PairingCapabilities;
|
|
67
|
+
createInvitation(): PairingInvitation;
|
|
68
|
+
info(code: string): Omit<PairingInvitation, 'code' | 'endpoint'>;
|
|
69
|
+
complete(input: {
|
|
70
|
+
code: string;
|
|
71
|
+
devicePublicKey: string;
|
|
72
|
+
origin: string;
|
|
73
|
+
scopes?: string[];
|
|
74
|
+
}): {
|
|
75
|
+
token: string;
|
|
76
|
+
clientId: string;
|
|
77
|
+
node: {
|
|
78
|
+
nodeId: string;
|
|
79
|
+
fingerprint: string;
|
|
80
|
+
network: string;
|
|
81
|
+
};
|
|
82
|
+
capabilities: PairingCapabilities;
|
|
83
|
+
};
|
|
84
|
+
identity(): {
|
|
85
|
+
nodeId: string;
|
|
86
|
+
fingerprint: string;
|
|
87
|
+
publicKey: string;
|
|
88
|
+
network: string;
|
|
89
|
+
capabilities: PairingCapabilities;
|
|
90
|
+
};
|
|
91
|
+
listClients(): LinkedClient[];
|
|
92
|
+
revoke(clientId: string): boolean;
|
|
93
|
+
allowsOrigin(origin?: string): boolean;
|
|
94
|
+
}
|
|
95
|
+
export declare function createPairingService(options: PairingServiceOptions): PairingService;
|
|
96
|
+
export interface PairingHttpServerOptions {
|
|
97
|
+
host?: string;
|
|
98
|
+
port: number;
|
|
99
|
+
maxBodyBytes?: number;
|
|
100
|
+
}
|
|
101
|
+
export interface PairingHttpServer {
|
|
102
|
+
listen(): Promise<{
|
|
103
|
+
host: string;
|
|
104
|
+
port: number;
|
|
105
|
+
}>;
|
|
106
|
+
close(): Promise<void>;
|
|
107
|
+
readonly address?: AddressInfo;
|
|
108
|
+
}
|
|
109
|
+
export declare function createPairingHttpServer(service: PairingService, options: PairingHttpServerOptions): PairingHttpServer;
|