@totemsdk/omnia-host 0.10.0 → 0.12.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.
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ObservationBus = void 0;
4
+ /**
5
+ * In-process live fan-out for SSE consumers. The durable journal remains the
6
+ * source of truth: a subscriber that misses a live event still replays it from
7
+ * `ObservationStore.readSince(cursor)`.
8
+ */
9
+ class ObservationBus {
10
+ listeners = new Set();
11
+ subscribe(listener) {
12
+ this.listeners.add(listener);
13
+ return () => this.listeners.delete(listener);
14
+ }
15
+ publish(observation) {
16
+ for (const listener of this.listeners) {
17
+ try {
18
+ listener(observation);
19
+ }
20
+ catch {
21
+ // A broken consumer must never break the mutation path.
22
+ }
23
+ }
24
+ }
25
+ get size() {
26
+ return this.listeners.size;
27
+ }
28
+ }
29
+ exports.ObservationBus = ObservationBus;
@@ -0,0 +1,61 @@
1
+ import type { OmniaChannel } from "@totemsdk/omnia";
2
+ import type { ObservationBus } from "./bus.js";
3
+ import type { ObservationDomain, ObservationEntity, ObservationPhase, ObservationRecord, ObservationStore, OmniaObservation } from "./types.js";
4
+ export interface ObservationEmitterConfig {
5
+ store: ObservationStore;
6
+ bus: ObservationBus;
7
+ networkId: string;
8
+ sourceNodeId: string;
9
+ streamId?: string;
10
+ /** Mark events from fixture/simulated hosts. */
11
+ simulated?: boolean;
12
+ /** Called on emission failures (defaults to console.error). */
13
+ onError?: (error: unknown) => void;
14
+ }
15
+ export interface EmitObservationInput {
16
+ domain: ObservationDomain;
17
+ kind: string;
18
+ phase: ObservationPhase;
19
+ entity: ObservationEntity;
20
+ occurredAt?: number;
21
+ operationId?: string;
22
+ causationId?: string;
23
+ transitionHash?: string;
24
+ parties?: {
25
+ local?: string;
26
+ remote?: string;
27
+ };
28
+ token?: {
29
+ tokenId: string;
30
+ scale: number;
31
+ };
32
+ before?: Record<string, unknown>;
33
+ after?: Record<string, unknown>;
34
+ amounts?: ObservationRecord["amounts"];
35
+ chain?: ObservationRecord["chain"];
36
+ evidence?: ObservationRecord["evidence"];
37
+ simulated?: boolean;
38
+ data?: Record<string, unknown>;
39
+ }
40
+ /** Stable hash for transition identity (never includes secrets). */
41
+ export declare function hashTransition(parts: Array<string | number | undefined | null>): string;
42
+ /** Hash a channel's signed commitment for cross-node correlation. */
43
+ export declare function channelTransitionHash(channel: OmniaChannel): string;
44
+ export declare function hashString(value: string): string;
45
+ /**
46
+ * Builds, redacts and persists observation events, then fans them out live.
47
+ *
48
+ * `emit()` never throws into a mutation: persistence/export failures are
49
+ * reported out-of-band so a payment can never be blocked by the observer.
50
+ */
51
+ export declare class ObservationEmitter {
52
+ private readonly store;
53
+ private readonly bus;
54
+ private readonly networkId;
55
+ private readonly sourceNodeId;
56
+ private readonly streamId;
57
+ private readonly simulated;
58
+ private readonly onError;
59
+ constructor(config: ObservationEmitterConfig);
60
+ emit(input: EmitObservationInput): Promise<OmniaObservation | undefined>;
61
+ }
@@ -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,5 @@
1
+ export * from './types.js';
2
+ export * from './bus.js';
3
+ export * from './emitter.js';
4
+ export * from './store.js';
5
+ export * from './snapshot.js';
@@ -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 });