@trillboards/edge-core 0.2.2 → 0.2.3

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,99 @@
1
+ /**
2
+ * StatusServer — HTTP server exposing device status, health, and Prometheus metrics.
3
+ *
4
+ * Runs inside the EdgeAgent process on a configurable port (default 9090).
5
+ * Uses ONLY Node.js built-in `http` module — zero external dependencies.
6
+ *
7
+ * Endpoints:
8
+ * GET /status — Full JSON status snapshot
9
+ * GET /health — Simple health probe (200 OK / 503 degraded)
10
+ * GET /metrics — Prometheus-format text metrics
11
+ */
12
+ export interface StatusServerOptions {
13
+ /** HTTP listen port. Default: 9090 */
14
+ port?: number;
15
+ /** HTTP listen host. Default: '0.0.0.0' */
16
+ host?: string;
17
+ }
18
+ export interface StatusData {
19
+ /** Device fingerprint */
20
+ fingerprint: string;
21
+ /** Assigned screen ID, or null if unassigned */
22
+ screenId: string | null;
23
+ /** Capability tier (1-4) */
24
+ tier: number;
25
+ /** Platform string, e.g. 'linux x64' */
26
+ platform: string;
27
+ /** Seconds since agent start (computed on read) */
28
+ uptime: number;
29
+ /** SDK version string */
30
+ sdkVersion: string;
31
+ /** Health snapshot from SubsystemHealthRegistry.toHeartbeatPayload() */
32
+ subsystemHealth: Record<string, unknown>;
33
+ /** Current sensing mode, e.g. 'edge_onnx', 'cloud', 'proxy_only' */
34
+ sensingMode: string;
35
+ /** Current detected face count */
36
+ currentFaceCount: number;
37
+ /** Average attention score (0-1) */
38
+ avgAttention: number;
39
+ /** Weighted VAS score (0-1) */
40
+ avgVAS: number;
41
+ /** Evidence grade, e.g. 'FACE_BACKED' */
42
+ evidenceGrade: string;
43
+ /** Number of signals currently buffered */
44
+ signalBufferCount: number;
45
+ /** Maximum buffer capacity */
46
+ signalBufferCapacity: number;
47
+ /** Whether the Socket.io connection is live */
48
+ socketConnected: boolean;
49
+ /** Epoch ms of last signal emit */
50
+ lastSignalTimestamp: number;
51
+ /** Total RSS memory usage in MB */
52
+ memoryUsageMb: number;
53
+ /** V8 heap used in MB */
54
+ heapUsedMb: number;
55
+ /** List of loaded model names, e.g. ['blazeFace', 'yamnet'] */
56
+ modelsLoaded: string[];
57
+ /** ONNX execution provider, e.g. 'cpu', 'openvino' */
58
+ executionProvider: string;
59
+ /** Whether federated learning is enabled */
60
+ federatedEnabled: boolean;
61
+ /** Epoch ms of last gradient upload */
62
+ lastGradientUpload: number;
63
+ }
64
+ export declare class StatusServer {
65
+ private server;
66
+ private statusData;
67
+ private startTime;
68
+ private port;
69
+ private host;
70
+ constructor(options?: StatusServerOptions);
71
+ /**
72
+ * Merge partial updates into the status snapshot.
73
+ * Call this from EdgeAgent whenever subsystem state changes.
74
+ */
75
+ updateStatus(partial: Partial<StatusData>): void;
76
+ /**
77
+ * Start the HTTP server. Resolves once the server is listening.
78
+ * If the port is already in use, logs a warning and resolves without starting.
79
+ */
80
+ start(): Promise<void>;
81
+ /**
82
+ * Stop the HTTP server gracefully. Resolves once all connections are closed.
83
+ */
84
+ stop(): Promise<void>;
85
+ /**
86
+ * Get the current status snapshot with computed fields (uptime, memory).
87
+ * Also used by the status CLI command via the /status HTTP endpoint.
88
+ */
89
+ getStatus(): StatusData & {
90
+ uptime: number;
91
+ memoryUsageMb: number;
92
+ heapUsedMb: number;
93
+ };
94
+ private handleRequest;
95
+ private handleStatus;
96
+ private handleHealth;
97
+ private handleMetrics;
98
+ }
99
+ //# sourceMappingURL=StatusServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatusServer.d.ts","sourceRoot":"","sources":["../src/StatusServer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IAGzB,yBAAyB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,UAAU,EAAE,MAAM,CAAC;IAInB,wEAAwE;IACxE,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAIzC,oEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,gBAAgB,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,aAAa,EAAE,MAAM,CAAC;IAItB,2CAA2C;IAC3C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,8BAA8B;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAI7B,+CAA+C;IAC/C,eAAe,EAAE,OAAO,CAAC;IACzB,mCAAmC;IACnC,mBAAmB,EAAE,MAAM,CAAC;IAI5B,mCAAmC;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,yBAAyB;IACzB,UAAU,EAAE,MAAM,CAAC;IAInB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,sDAAsD;IACtD,iBAAiB,EAAE,MAAM,CAAC;IAI1B,4CAA4C;IAC5C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,uCAAuC;IACvC,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AA2BD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;gBAET,OAAO,CAAC,EAAE,mBAAmB;IAOzC;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI;IAIhD;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkC5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAW3B;;;OAGG;IACH,SAAS,IAAI,UAAU,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAcvF,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,YAAY;IAoBpB,OAAO,CAAC,aAAa;CAyDtB"}
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ /**
3
+ * StatusServer — HTTP server exposing device status, health, and Prometheus metrics.
4
+ *
5
+ * Runs inside the EdgeAgent process on a configurable port (default 9090).
6
+ * Uses ONLY Node.js built-in `http` module — zero external dependencies.
7
+ *
8
+ * Endpoints:
9
+ * GET /status — Full JSON status snapshot
10
+ * GET /health — Simple health probe (200 OK / 503 degraded)
11
+ * GET /metrics — Prometheus-format text metrics
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.StatusServer = void 0;
38
+ const http = __importStar(require("http"));
39
+ const os = __importStar(require("os"));
40
+ const DEFAULT_STATUS = {
41
+ fingerprint: '',
42
+ screenId: null,
43
+ tier: 0,
44
+ platform: `${os.platform()} ${os.arch()}`,
45
+ uptime: 0,
46
+ sdkVersion: '0.2.2',
47
+ subsystemHealth: {},
48
+ sensingMode: 'unknown',
49
+ currentFaceCount: 0,
50
+ avgAttention: 0,
51
+ avgVAS: 0,
52
+ evidenceGrade: 'EMPTY',
53
+ signalBufferCount: 0,
54
+ signalBufferCapacity: 8640,
55
+ socketConnected: false,
56
+ lastSignalTimestamp: 0,
57
+ memoryUsageMb: 0,
58
+ heapUsedMb: 0,
59
+ modelsLoaded: [],
60
+ executionProvider: 'cpu',
61
+ federatedEnabled: false,
62
+ lastGradientUpload: 0,
63
+ };
64
+ class StatusServer {
65
+ constructor(options) {
66
+ this.server = null;
67
+ this.port = options?.port ?? 9090;
68
+ this.host = options?.host ?? '0.0.0.0';
69
+ this.startTime = Date.now();
70
+ this.statusData = { ...DEFAULT_STATUS };
71
+ }
72
+ /**
73
+ * Merge partial updates into the status snapshot.
74
+ * Call this from EdgeAgent whenever subsystem state changes.
75
+ */
76
+ updateStatus(partial) {
77
+ Object.assign(this.statusData, partial);
78
+ }
79
+ /**
80
+ * Start the HTTP server. Resolves once the server is listening.
81
+ * If the port is already in use, logs a warning and resolves without starting.
82
+ */
83
+ async start() {
84
+ if (this.server)
85
+ return;
86
+ const server = http.createServer((req, res) => {
87
+ this.handleRequest(req, res);
88
+ });
89
+ return new Promise((resolve) => {
90
+ server.once('error', (err) => {
91
+ if (err.code === 'EADDRINUSE') {
92
+ console.warn(`[StatusServer] Port ${this.port} is already in use — status server not started`);
93
+ this.server = null;
94
+ resolve();
95
+ }
96
+ else {
97
+ console.warn(`[StatusServer] Failed to start: ${err.message}`);
98
+ this.server = null;
99
+ resolve();
100
+ }
101
+ });
102
+ server.listen(this.port, this.host, () => {
103
+ this.server = server;
104
+ console.log(`[StatusServer] Listening on http://${this.host}:${this.port}`);
105
+ resolve();
106
+ });
107
+ });
108
+ }
109
+ /**
110
+ * Stop the HTTP server gracefully. Resolves once all connections are closed.
111
+ */
112
+ async stop() {
113
+ if (!this.server)
114
+ return;
115
+ return new Promise((resolve) => {
116
+ this.server.close(() => {
117
+ this.server = null;
118
+ resolve();
119
+ });
120
+ });
121
+ }
122
+ /**
123
+ * Get the current status snapshot with computed fields (uptime, memory).
124
+ * Also used by the status CLI command via the /status HTTP endpoint.
125
+ */
126
+ getStatus() {
127
+ const mem = process.memoryUsage();
128
+ const uptimeSeconds = Math.floor((Date.now() - this.startTime) / 1000);
129
+ return {
130
+ ...this.statusData,
131
+ uptime: uptimeSeconds,
132
+ memoryUsageMb: parseFloat((mem.rss / (1024 * 1024)).toFixed(1)),
133
+ heapUsedMb: parseFloat((mem.heapUsed / (1024 * 1024)).toFixed(1)),
134
+ };
135
+ }
136
+ // ---- Private request handler ----
137
+ handleRequest(req, res) {
138
+ // CORS headers for local dev tooling
139
+ res.setHeader('Access-Control-Allow-Origin', '*');
140
+ res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
141
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
142
+ if (req.method === 'OPTIONS') {
143
+ res.writeHead(204);
144
+ res.end();
145
+ return;
146
+ }
147
+ // Parse URL path (strip query string)
148
+ const urlPath = (req.url || '/').split('?')[0];
149
+ switch (urlPath) {
150
+ case '/status':
151
+ this.handleStatus(res);
152
+ break;
153
+ case '/health':
154
+ this.handleHealth(res);
155
+ break;
156
+ case '/metrics':
157
+ this.handleMetrics(res);
158
+ break;
159
+ default:
160
+ res.writeHead(404, { 'Content-Type': 'application/json' });
161
+ res.end(JSON.stringify({ error: 'Not Found', path: urlPath }));
162
+ break;
163
+ }
164
+ }
165
+ handleStatus(res) {
166
+ const status = this.getStatus();
167
+ res.writeHead(200, { 'Content-Type': 'application/json' });
168
+ res.end(JSON.stringify(status));
169
+ }
170
+ handleHealth(res) {
171
+ const status = this.getStatus();
172
+ const failedSubsystems = [];
173
+ for (const [name, info] of Object.entries(status.subsystemHealth)) {
174
+ const sub = info;
175
+ if (sub.stale === true || isErrorStatus(sub.status)) {
176
+ failedSubsystems.push(name);
177
+ }
178
+ }
179
+ if (failedSubsystems.length > 0) {
180
+ res.writeHead(503, { 'Content-Type': 'application/json' });
181
+ res.end(JSON.stringify({ status: 'degraded', failed: failedSubsystems }));
182
+ }
183
+ else {
184
+ res.writeHead(200, { 'Content-Type': 'application/json' });
185
+ res.end(JSON.stringify({ status: 'ok' }));
186
+ }
187
+ }
188
+ handleMetrics(res) {
189
+ const status = this.getStatus();
190
+ const lines = [
191
+ '# HELP trillboards_face_count Current detected face count',
192
+ '# TYPE trillboards_face_count gauge',
193
+ `trillboards_face_count ${status.currentFaceCount}`,
194
+ '',
195
+ '# HELP trillboards_attention_score Average attention score',
196
+ '# TYPE trillboards_attention_score gauge',
197
+ `trillboards_attention_score ${status.avgAttention}`,
198
+ '',
199
+ '# HELP trillboards_vas_score Weighted VAS score',
200
+ '# TYPE trillboards_vas_score gauge',
201
+ `trillboards_vas_score ${status.avgVAS}`,
202
+ '',
203
+ '# HELP trillboards_signal_buffer_count Buffered signals waiting to send',
204
+ '# TYPE trillboards_signal_buffer_count gauge',
205
+ `trillboards_signal_buffer_count ${status.signalBufferCount}`,
206
+ '',
207
+ '# HELP trillboards_uptime_seconds Agent uptime in seconds',
208
+ '# TYPE trillboards_uptime_seconds gauge',
209
+ `trillboards_uptime_seconds ${status.uptime}`,
210
+ '',
211
+ '# HELP trillboards_heap_used_mb V8 heap used in MB',
212
+ '# TYPE trillboards_heap_used_mb gauge',
213
+ `trillboards_heap_used_mb ${status.heapUsedMb}`,
214
+ '',
215
+ '# HELP trillboards_memory_rss_mb Process RSS memory in MB',
216
+ '# TYPE trillboards_memory_rss_mb gauge',
217
+ `trillboards_memory_rss_mb ${status.memoryUsageMb}`,
218
+ '',
219
+ '# HELP trillboards_signal_buffer_capacity Maximum signal buffer capacity',
220
+ '# TYPE trillboards_signal_buffer_capacity gauge',
221
+ `trillboards_signal_buffer_capacity ${status.signalBufferCapacity}`,
222
+ '',
223
+ '# HELP trillboards_socket_connected Socket.io connection status (1=connected, 0=disconnected)',
224
+ '# TYPE trillboards_socket_connected gauge',
225
+ `trillboards_socket_connected ${status.socketConnected ? 1 : 0}`,
226
+ '',
227
+ '# HELP trillboards_federated_enabled Federated learning enabled (1=yes, 0=no)',
228
+ '# TYPE trillboards_federated_enabled gauge',
229
+ `trillboards_federated_enabled ${status.federatedEnabled ? 1 : 0}`,
230
+ '',
231
+ '# HELP trillboards_tier Device capability tier',
232
+ '# TYPE trillboards_tier gauge',
233
+ `trillboards_tier ${status.tier}`,
234
+ '',
235
+ '# HELP trillboards_models_loaded_count Number of loaded ML models',
236
+ '# TYPE trillboards_models_loaded_count gauge',
237
+ `trillboards_models_loaded_count ${status.modelsLoaded.length}`,
238
+ '',
239
+ ];
240
+ res.writeHead(200, { 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8' });
241
+ res.end(lines.join('\n'));
242
+ }
243
+ }
244
+ exports.StatusServer = StatusServer;
245
+ /**
246
+ * Check if a subsystem status string represents an error condition.
247
+ * Matches the ERROR_STATUSES set in SubsystemHealthRegistry.
248
+ */
249
+ function isErrorStatus(status) {
250
+ const errorStatuses = new Set([
251
+ 'DISCONNECTED',
252
+ 'STUCK',
253
+ 'CRASHED',
254
+ 'PERMISSION_DENIED',
255
+ 'ERROR',
256
+ 'CRITICAL',
257
+ 'OFFLINE',
258
+ 'HIGH',
259
+ ]);
260
+ return errorStatuses.has(status);
261
+ }
262
+ //# sourceMappingURL=StatusServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatusServer.js","sourceRoot":"","sources":["../src/StatusServer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * ZKAudienceProver — Zero-Knowledge proof system for audience measurement.
3
+ *
4
+ * Proves that aggregate audience metrics are consistent with individual
5
+ * observations WITHOUT revealing individual observation data.
6
+ *
7
+ * Since snarkjs is a heavy dependency and may not be available on all edge
8
+ * devices, this implementation uses a commitment-based approach with
9
+ * Pedersen-like commitments using standard crypto (SHA-256 + HMAC), providing
10
+ * a practical ZK-adjacent proof system that can be upgraded to full ZK-SNARKs
11
+ * when snarkjs is available.
12
+ *
13
+ * Proof generation (on device):
14
+ * 1. Commit to each observation via HMAC-SHA256(secretSeed, observation)
15
+ * 2. Build a Merkle tree from all commitments (root proves the set)
16
+ * 3. Compute aggregate hash: SHA256(aggregate + rangeProofNonce)
17
+ * 4. Bind range constraints via rangeProofNonce derived from secret + aggregate
18
+ * 5. Chain all commitments into a single observationCommitment hash
19
+ *
20
+ * Verification (server-side, no device secret needed):
21
+ * 1. Verify aggregateHash matches SHA256(aggregate + rangeProofNonce)
22
+ * 2. Verify leafCount matches claimed faceCount
23
+ * 3. Verify range constraints (attention in [0,1], faceCount >= 0, etc.)
24
+ * 4. Verify proof version and timestamp are reasonable
25
+ * 5. Individual observations remain private (cannot be recovered)
26
+ *
27
+ * Zero external dependencies — uses Node.js built-in crypto module only.
28
+ */
29
+ export interface AudienceObservation {
30
+ faceId: number;
31
+ attention: number;
32
+ age: number;
33
+ gender: 'male' | 'female' | null;
34
+ dwellMs: number;
35
+ timestamp: number;
36
+ }
37
+ export interface AudienceAggregate {
38
+ faceCount: number;
39
+ avgAttention: number;
40
+ maleCount: number;
41
+ femaleCount: number;
42
+ avgAge: number;
43
+ totalDwellMs: number;
44
+ windowStart: number;
45
+ windowEnd: number;
46
+ }
47
+ export interface AudienceProof {
48
+ /** SHA-256 of all individual observation commitment hashes chained together. */
49
+ observationCommitment: string;
50
+ /** SHA-256 of the aggregate values bound with rangeProofNonce. */
51
+ aggregateHash: string;
52
+ /** Random nonce derived from secret + aggregate for range proof binding. */
53
+ rangeProofNonce: string;
54
+ /** Root of the Merkle tree built from individual observation commitments. */
55
+ merkleRoot: string;
56
+ /** Number of leaves (observations) in the Merkle tree. */
57
+ leafCount: number;
58
+ /** Proof scheme version identifier. */
59
+ proofVersion: string;
60
+ /** Timestamp when the proof was generated. */
61
+ generatedAt: number;
62
+ /** Device fingerprint that produced this proof. */
63
+ deviceFingerprint: string;
64
+ }
65
+ export interface VerificationResult {
66
+ valid: boolean;
67
+ checks: {
68
+ /** Whether the aggregateHash matches re-computed SHA256(aggregate + nonce). */
69
+ aggregateConsistency: boolean;
70
+ /** Whether the observationCommitment is structurally valid (non-empty hex). */
71
+ commitmentIntegrity: boolean;
72
+ /** Whether aggregate values are in expected ranges. */
73
+ rangeValidity: boolean;
74
+ /** Whether the merkleRoot is consistent with the claimed leafCount. */
75
+ merkleIntegrity: boolean;
76
+ };
77
+ verifiedAt: number;
78
+ }
79
+ export declare class ZKAudienceProver {
80
+ private readonly deviceFingerprint;
81
+ private readonly secretSeed;
82
+ /**
83
+ * @param deviceFingerprint - Unique identifier for the device producing proofs.
84
+ * @param secretSeed - Device-specific secret for commitment generation.
85
+ * If not provided, a cryptographically random 32-byte seed is generated.
86
+ */
87
+ constructor(deviceFingerprint: string, secretSeed?: string);
88
+ /**
89
+ * Generate a ZK proof that the aggregate is consistent with individual
90
+ * observations without revealing the individual data.
91
+ *
92
+ * Algorithm:
93
+ * 1. Commit to each observation: HMAC-SHA256(secretSeed, serialize(obs_i))
94
+ * 2. Build a Merkle tree from all commitments; root proves the set.
95
+ * 3. Derive rangeProofNonce from secretSeed + aggregate fields (binds
96
+ * range constraints to the proof without revealing the secret).
97
+ * 4. Compute aggregateHash = SHA256(serialize(aggregate) + rangeProofNonce).
98
+ * 5. Chain all commitments into observationCommitment =
99
+ * SHA256(commitment_0 + commitment_1 + ... + commitment_n).
100
+ *
101
+ * @param observations - Individual face observations in the window.
102
+ * @param aggregate - Pre-computed aggregate of those observations.
103
+ * @returns AudienceProof that can be verified server-side.
104
+ */
105
+ prove(observations: AudienceObservation[], aggregate: AudienceAggregate): Promise<AudienceProof>;
106
+ /**
107
+ * Server-side verification of an audience proof.
108
+ *
109
+ * Since the server does not have the device secret, it cannot reconstruct
110
+ * individual observation commitments. However, it CAN verify:
111
+ * 1. aggregateHash matches SHA256(serialize(aggregate) + rangeProofNonce)
112
+ * 2. leafCount matches aggregate.faceCount
113
+ * 3. All aggregate values are in expected ranges
114
+ * 4. Proof metadata (version, timestamp) are reasonable
115
+ * 5. merkleRoot and observationCommitment are structurally valid
116
+ *
117
+ * Individual observations remain private (the point of ZK).
118
+ *
119
+ * @param aggregate - The claimed aggregate values.
120
+ * @param proof - The proof generated by a device.
121
+ * @returns VerificationResult with per-check breakdown.
122
+ */
123
+ static verify(aggregate: AudienceAggregate, proof: AudienceProof): Promise<VerificationResult>;
124
+ /**
125
+ * Compute an AudienceAggregate from a set of individual observations.
126
+ *
127
+ * This is a utility for consistency: the device calls this to build the
128
+ * aggregate, then passes both observations and aggregate to prove().
129
+ *
130
+ * @param observations - Individual face observations.
131
+ * @param windowStart - Start of the aggregation window (epoch ms).
132
+ * @param windowEnd - End of the aggregation window (epoch ms).
133
+ * @returns AudienceAggregate computed from the observations.
134
+ */
135
+ static computeAggregate(observations: AudienceObservation[], windowStart: number, windowEnd: number): AudienceAggregate;
136
+ }
137
+ //# sourceMappingURL=ZKAudienceProver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZKAudienceProver.d.ts","sourceRoot":"","sources":["../src/ZKAudienceProver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,qBAAqB,EAAE,MAAM,CAAC;IAC9B,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,eAAe,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE;QACN,+EAA+E;QAC/E,oBAAoB,EAAE,OAAO,CAAC;QAC9B,+EAA+E;QAC/E,mBAAmB,EAAE,OAAO,CAAC;QAC7B,uDAAuD;QACvD,aAAa,EAAE,OAAO,CAAC;QACvB,uEAAuE;QACvE,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,UAAU,EAAE,MAAM,CAAC;CACpB;AAyGD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC;;;;OAIG;gBACS,iBAAiB,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;IAK1D;;;;;;;;;;;;;;;;OAgBG;IACG,KAAK,CACT,YAAY,EAAE,mBAAmB,EAAE,EACnC,SAAS,EAAE,iBAAiB,GAC3B,OAAO,CAAC,aAAa,CAAC;IA8CzB;;;;;;;;;;;;;;;;OAgBG;WACU,MAAM,CACjB,SAAS,EAAE,iBAAiB,EAC5B,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC,kBAAkB,CAAC;IA6E9B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,gBAAgB,CACrB,YAAY,EAAE,mBAAmB,EAAE,EACnC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,iBAAiB;CA6CrB"}