@keri-ts/verifier 0.0.0-stage → 2.0.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.
@@ -0,0 +1,515 @@
1
+ /**
2
+ * Sally-like verifier agent pipeline.
3
+ *
4
+ * Source of truth:
5
+ * - accepted `/ipex/grant` EXNs in `Baser.exns`
6
+ * - embedded credential/TEL/KEL artifacts in `Baser.epath`
7
+ * - saved credentials and TEL state in `Reger`
8
+ * - TEL revocation cues from the shared runtime cue deck
9
+ *
10
+ * `Notifier` rows are intentionally not consumed here; they remain operator
11
+ * visibility, matching the roadmap contract.
12
+ */
13
+ import { Dater, Ilks, Prefixer, Vrsn_1_0 } from "cesr-ts";
14
+ import { credentialSaidFromGrant, IPEX_GRANT_ROUTE, makeNowIso8601, processCredentialPresentationArtifacts, storedGrantArtifacts, ValidationError, } from "keri-ts/runtime";
15
+ import { validateCredential } from "./policy.js";
16
+ import { postWebhook, webhookBody } from "./webhook.js";
17
+ const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
18
+ /** Sally-like verifier processor for one `Habery`/`Reger` runtime. */
19
+ export class VerifierAgent {
20
+ /**
21
+ * Borrow the configured stores, protocol owner and webhook policy; open no runtime or background worker.
22
+ *
23
+ * @param hby Borrowed local identities and accepted-state stores.
24
+ * @param reger Borrowed credential/TEL evidence store.
25
+ * @param cdb Borrowed durable pending/ready/acknowledged webhook store.
26
+ * @param protocol Borrowed shared processor for grant prerequisites and scheduled escrow advancement.
27
+ * @param cues Application cue Deck; only matching revocation cues are consumed.
28
+ * @param http Borrowed HTTP implementation selected by composition.
29
+ * @param now Required epoch-millisecond function, bound to its clock; used for local admission, webhook headers and expiry.
30
+ * @param validators Schema-SAID-to-business-validator table, shallow-copied at construction.
31
+ * @param options Webhook URL, child genus, expiry window (default ten minutes), and known-schema policy.
32
+ */
33
+ constructor(hby, reger, cdb, protocol, cues, http, now, validators, options) {
34
+ /** Borrowed identities and accepted exchange/KEL state. */
35
+ Object.defineProperty(this, "hby", {
36
+ enumerable: true,
37
+ configurable: true,
38
+ writable: true,
39
+ value: void 0
40
+ });
41
+ /** Borrowed credential and TEL evidence store used to establish current credential state. */
42
+ Object.defineProperty(this, "reger", {
43
+ enumerable: true,
44
+ configurable: true,
45
+ writable: true,
46
+ value: void 0
47
+ });
48
+ /** Verifier workflow store for pending, ready and acknowledged webhook records. */
49
+ Object.defineProperty(this, "cdb", {
50
+ enumerable: true,
51
+ configurable: true,
52
+ writable: true,
53
+ value: void 0
54
+ });
55
+ /** Borrowed protocol dispatch and escrow owner; artifact intake owns its own parser and Kevery. */
56
+ Object.defineProperty(this, "protocol", {
57
+ enumerable: true,
58
+ configurable: true,
59
+ writable: true,
60
+ value: void 0
61
+ });
62
+ /** Application cue flow; this owner consumes revocation cues and preserves unrelated records. */
63
+ Object.defineProperty(this, "cues", {
64
+ enumerable: true,
65
+ configurable: true,
66
+ writable: true,
67
+ value: void 0
68
+ });
69
+ /** Borrowed scoped HTTP client used for webhook attempts. */
70
+ Object.defineProperty(this, "http", {
71
+ enumerable: true,
72
+ configurable: true,
73
+ writable: true,
74
+ value: void 0
75
+ });
76
+ /** Borrowed epoch-millisecond source used for webhook timestamps and expiry. */
77
+ Object.defineProperty(this, "now", {
78
+ enumerable: true,
79
+ configurable: true,
80
+ writable: true,
81
+ value: void 0
82
+ });
83
+ /** Configured webhook destination, independent of protocol peer routing. */
84
+ Object.defineProperty(this, "hook", {
85
+ enumerable: true,
86
+ configurable: true,
87
+ writable: true,
88
+ value: void 0
89
+ });
90
+ /** Attachment genus used when settling accepted grant artifacts. */
91
+ Object.defineProperty(this, "gvrsn", {
92
+ enumerable: true,
93
+ configurable: true,
94
+ writable: true,
95
+ value: void 0
96
+ });
97
+ /** Existing workflow expiry window; elapsed time is measured by the injected clock. */
98
+ Object.defineProperty(this, "timeoutMs", {
99
+ enumerable: true,
100
+ configurable: true,
101
+ writable: true,
102
+ value: void 0
103
+ });
104
+ /** Schema-specific business checks copied from configuration at construction. */
105
+ Object.defineProperty(this, "validators", {
106
+ enumerable: true,
107
+ configurable: true,
108
+ writable: true,
109
+ value: void 0
110
+ });
111
+ /** Reject credentials without a configured schema validator when explicitly enabled. */
112
+ Object.defineProperty(this, "requireKnownSchemas", {
113
+ enumerable: true,
114
+ configurable: true,
115
+ writable: true,
116
+ value: void 0
117
+ });
118
+ Object.defineProperty(this, "acdcV2", {
119
+ enumerable: true,
120
+ configurable: true,
121
+ writable: true,
122
+ value: void 0
123
+ });
124
+ this.hby = hby;
125
+ this.reger = reger;
126
+ this.cdb = cdb;
127
+ this.protocol = protocol;
128
+ this.cues = cues;
129
+ this.http = http;
130
+ this.now = now;
131
+ this.hook = options.hook;
132
+ this.gvrsn = options.gvrsn ?? Vrsn_1_0;
133
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
134
+ this.validators = { ...validators };
135
+ this.requireKnownSchemas = options.requireKnownSchemas ?? false;
136
+ this.acdcV2 = options.acdcV2;
137
+ }
138
+ /**
139
+ * Settle verified artifacts and deliver queued webhooks within the caller's operation.
140
+ *
141
+ * @returns Per-pass admission, readiness, webhook and rejection counts; no worker is started.
142
+ */
143
+ *processOnce() {
144
+ const result = {
145
+ grantsQueued: 0,
146
+ revocationsQueued: 0,
147
+ presentationsReady: 0,
148
+ revocationsReady: 0,
149
+ webhooksSent: 0,
150
+ webhooksFailed: 0,
151
+ rejected: 0,
152
+ };
153
+ // Settle grant evidence and capture revocations before advancing shared protocol dependencies.
154
+ result.grantsQueued += yield* this.scanAcceptedGrants();
155
+ yield* this.processAcdcV2Grants(result);
156
+ result.revocationsQueued += this.processRevokedCues();
157
+ yield* this.protocol.processEscrowsOnce();
158
+ result.revocationsQueued += this.scanPersistedRevocations();
159
+ // Project accepted evidence into ready work, then observe real webhook delivery outcomes.
160
+ const presentations = this.processPresentations();
161
+ result.presentationsReady += presentations.ready;
162
+ result.rejected += presentations.rejected;
163
+ result.revocationsReady += this.processRevocations();
164
+ result.webhooksSent += yield* this.processReady(this.cdb.recv, "iss", result);
165
+ result.webhooksSent += yield* this.processReady(this.cdb.revk, "rev", result);
166
+ return result;
167
+ }
168
+ /**
169
+ * Settle grant artifacts before queuing their verified credentials for webhook delivery.
170
+ *
171
+ * @returns Number of newly queued presentations after artifact intake and verification.
172
+ */
173
+ *scanAcceptedGrants() {
174
+ let queued = 0;
175
+ for (const [, grant] of this.hby.db.exns.getTopItemIter()) {
176
+ if (grant.route !== IPEX_GRANT_ROUTE || !grant.said || grant.pvrsn.major === 2) {
177
+ continue;
178
+ }
179
+ const credentialSaid = credentialSaidFromGrant(grant);
180
+ if (!credentialSaid || this.hasQueuedOrSent(credentialSaid)) {
181
+ continue;
182
+ }
183
+ const artifacts = storedGrantArtifacts(this.hby, grant, this.gvrsn);
184
+ yield* processCredentialPresentationArtifacts(this.protocol, artifacts);
185
+ const creder = this.reger.creds.get([credentialSaid]);
186
+ if (!creder) {
187
+ throw new ValidationError(`Credential ${credentialSaid} was not saved after grant artifact processing.`);
188
+ }
189
+ const issuer = requireString(creder.issuer, "credential issuer");
190
+ this.cdb.snd.pin([credentialSaid], new Prefixer({ qb64: issuer }));
191
+ this.cdb.iss.pin([credentialSaid], nowDater(this.now()));
192
+ queued += 1;
193
+ }
194
+ return queued;
195
+ }
196
+ /** ACDC v2 work is keyed by exact Grant, preserving distinct retained partial/full views in the existing tables. */
197
+ *processAcdcV2Grants(result) {
198
+ if (!this.acdcV2)
199
+ return;
200
+ for (const [, grant] of this.hby.db.exns.getTopItemIter()) {
201
+ if (grant.route !== IPEX_GRANT_ROUTE || grant.pvrsn.major !== 2 || !grant.said)
202
+ continue;
203
+ const said = grant.said;
204
+ if (this.cdb.rack.get([said]))
205
+ continue;
206
+ if (!this.hasQueuedOrSent(said)) {
207
+ this.cdb.iss.pin([said], nowDater(this.now()));
208
+ result.grantsQueued++;
209
+ }
210
+ const verification = yield* this.acdcV2.verifyCurrent(said), creder = verification.credential;
211
+ if (verification.kind === "missing")
212
+ continue;
213
+ if (verification.kind === "refused" || !creder) {
214
+ this.cdb.iss.rem([said]);
215
+ this.cdb.recv.trim([said]);
216
+ result.rejected++;
217
+ continue;
218
+ }
219
+ const action = verification.kind === "revoked" ? "rev" : "iss";
220
+ if (action === "rev" && !this.cdb.ack.get([said])) {
221
+ this.cdb.iss.rem([said]);
222
+ this.cdb.recv.trim([said]);
223
+ result.rejected++;
224
+ continue;
225
+ }
226
+ if (action === "iss" && this.cdb.ack.get([said]))
227
+ continue;
228
+ if (action === "iss"
229
+ && validateCredential(creder, this.validators, { hby: this.hby, reger: this.reger }, this.requireKnownSchemas)) {
230
+ this.cdb.iss.rem([said]);
231
+ result.rejected++;
232
+ continue;
233
+ }
234
+ const ready = action === "iss" ? this.cdb.recv : this.cdb.revk;
235
+ const waiting = [...ready.getTopItemIter([said, ""])][0];
236
+ const dater = this.cdb.iss.get([said]) ?? (waiting ? new Dater({ qb64: waiting[0][1] }) : nowDater(this.now()));
237
+ if (this.expired(dater)) {
238
+ this.cdb.iss.rem([said]);
239
+ ready.trim([said]);
240
+ continue;
241
+ }
242
+ ready.pin([said, dater.qb64], creder);
243
+ this.cdb.snd.pin([said], new Prefixer({ qb64: creder.issuer }));
244
+ this.cdb.iss.rem([said]);
245
+ if (action === "iss")
246
+ result.presentationsReady++;
247
+ else
248
+ result.revocationsReady++;
249
+ const payload = webhookBody(creder, action, null);
250
+ payload.data.grant = said;
251
+ payload.data.presenter = grant.pre;
252
+ payload.data.verifier = this.acdcV2.hab.pre;
253
+ payload.data.observations = verification.observations;
254
+ if (yield* postWebhook(this.http, this.now, this.hook, payload)) {
255
+ ready.rem([said, dater.qb64]);
256
+ (action === "iss" ? this.cdb.ack : this.cdb.rack).pin([said], creder);
257
+ result.webhooksSent++;
258
+ }
259
+ else
260
+ result.webhooksFailed++;
261
+ }
262
+ }
263
+ /** Consume a finite revocation-cue pass, restoring unrelated and failed records in their original order. */
264
+ processRevokedCues() {
265
+ const retained = [];
266
+ const count = this.cues.length;
267
+ let queued = 0;
268
+ try {
269
+ for (let index = 0; index < count; index++) {
270
+ const cue = this.cues.pull();
271
+ if (!cue)
272
+ break;
273
+ if (cue.kin !== "revoked")
274
+ retained.push(cue);
275
+ else {
276
+ try {
277
+ queued += this.queueRevocation(cue);
278
+ }
279
+ catch (error) {
280
+ retained.push(cue);
281
+ throw error;
282
+ }
283
+ }
284
+ }
285
+ }
286
+ finally {
287
+ for (const cue of retained.reverse())
288
+ this.cues.prepend(cue);
289
+ }
290
+ return queued;
291
+ }
292
+ /**
293
+ * Publish existing durable revocation work synchronously; caller retains source ownership through failure.
294
+ *
295
+ * @param cue Accepted credential revocation observation; uses injected local time for workflow admission.
296
+ * @returns One for newly queued work, otherwise zero.
297
+ */
298
+ queueRevocation(cue) {
299
+ const said = cue.serder.pre;
300
+ if (!said || this.hasRevocationQueuedOrSent(said))
301
+ return 0;
302
+ const creder = this.reger.creds.get([said]);
303
+ if (creder?.issuer)
304
+ this.cdb.snd.pin([said], new Prefixer({ qb64: creder.issuer }));
305
+ this.cdb.rev.pin([said], nowDater(this.now()));
306
+ return 1;
307
+ }
308
+ /**
309
+ * Recover revocation work from saved credentials backed by an accepted IPEX grant.
310
+ *
311
+ * @returns Number of revocations recovered from accepted persisted evidence.
312
+ */
313
+ scanPersistedRevocations() {
314
+ let queued = 0;
315
+ for (const [keys] of this.reger.saved.getTopItemIter()) {
316
+ const said = keys[0];
317
+ if (!said || this.hasRevocationQueuedOrSent(said)) {
318
+ continue;
319
+ }
320
+ const creder = this.reger.creds.get([said]);
321
+ if (!creder || !this.hasAcceptedGrantForCredential(said)) {
322
+ continue;
323
+ }
324
+ const state = credentialState(this.reger, creder);
325
+ if (!state || (state.et !== Ilks.rev && state.et !== Ilks.brv)) {
326
+ continue;
327
+ }
328
+ if (creder.issuer) {
329
+ this.cdb.snd.pin([said], new Prefixer({ qb64: creder.issuer }));
330
+ }
331
+ this.cdb.rev.pin([said], nowDater(this.now()));
332
+ queued += 1;
333
+ }
334
+ return queued;
335
+ }
336
+ /** Move accepted, unexpired and business-valid credentials into the ready presentation store. */
337
+ processPresentations() {
338
+ let ready = 0;
339
+ let rejected = 0;
340
+ for (const [keys, dater] of [...this.cdb.iss.getTopItemIter()]) {
341
+ const said = keys[0];
342
+ if (!said) {
343
+ continue;
344
+ }
345
+ if (this.expired(dater)) {
346
+ this.cdb.iss.rem([said]);
347
+ continue;
348
+ }
349
+ if (this.reger.saved.get([said]) === null) {
350
+ continue;
351
+ }
352
+ const creder = this.reger.creds.get([said]);
353
+ if (!creder) {
354
+ continue;
355
+ }
356
+ const state = credentialState(this.reger, creder);
357
+ if (!state) {
358
+ continue;
359
+ }
360
+ if (state.et === Ilks.rev || state.et === Ilks.brv) {
361
+ this.cdb.iss.rem([said]);
362
+ continue;
363
+ }
364
+ const rejection = validateCredential(creder, this.validators, { hby: this.hby, reger: this.reger }, this.requireKnownSchemas);
365
+ if (rejection) {
366
+ this.cdb.iss.rem([said]);
367
+ rejected += 1;
368
+ continue;
369
+ }
370
+ this.cdb.recv.pin([said, dater.qb64], creder);
371
+ this.cdb.iss.rem([said]);
372
+ ready += 1;
373
+ }
374
+ return { ready, rejected };
375
+ }
376
+ /** Move confirmed revocations into the ready revocation store, retaining unresolved evidence. */
377
+ processRevocations() {
378
+ let ready = 0;
379
+ for (const [keys, dater] of [...this.cdb.rev.getTopItemIter()]) {
380
+ const said = keys[0];
381
+ if (!said) {
382
+ continue;
383
+ }
384
+ if (this.expired(dater)) {
385
+ this.cdb.rev.rem([said]);
386
+ continue;
387
+ }
388
+ const creder = this.reger.creds.get([said]);
389
+ if (!creder) {
390
+ continue;
391
+ }
392
+ const state = credentialState(this.reger, creder);
393
+ if (!state || state.et === Ilks.iss || state.et === Ilks.bis) {
394
+ continue;
395
+ }
396
+ if (state.et === Ilks.rev || state.et === Ilks.brv) {
397
+ if (creder.issuer) {
398
+ this.cdb.snd.pin([said], new Prefixer({ qb64: creder.issuer }));
399
+ }
400
+ this.cdb.rev.rem([said]);
401
+ this.cdb.revk.pin([said, dater.qb64], creder);
402
+ ready += 1;
403
+ }
404
+ }
405
+ return ready;
406
+ }
407
+ /** Deliver each ready webhook before recording success or applying its expiry policy. */
408
+ *processReady(db, action, result) {
409
+ let sent = 0;
410
+ for (const [keys, creder] of [...db.getTopItemIter()]) {
411
+ const [said, daterQb64] = keys;
412
+ if (said && this.hby.db.exns.get([said])?.pvrsn.major === 2)
413
+ continue;
414
+ if (!said || !daterQb64) {
415
+ continue;
416
+ }
417
+ let state = null;
418
+ if (action === "rev")
419
+ state = credentialState(this.reger, creder);
420
+ const payload = webhookBody(creder, action, state);
421
+ const accepted = yield* postWebhook(this.http, this.now, this.hook, payload);
422
+ if (accepted) {
423
+ db.rem([said, daterQb64]);
424
+ if (action === "iss") {
425
+ this.cdb.ack.pin([said], creder);
426
+ }
427
+ else {
428
+ this.cdb.rack.pin([said], creder);
429
+ }
430
+ sent += 1;
431
+ continue;
432
+ }
433
+ result.webhooksFailed += 1;
434
+ const dater = new Dater({ qb64: daterQb64 });
435
+ if (this.expired(dater)) {
436
+ db.rem([said, daterQb64]);
437
+ }
438
+ }
439
+ return sent;
440
+ }
441
+ /** Avoid duplicate presentation work already pending, ready or acknowledged. */
442
+ hasQueuedOrSent(said) {
443
+ return this.cdb.iss.get([said]) !== null
444
+ || this.cdb.ack.get([said]) !== null
445
+ || this.hasReady(this.cdb.recv, said);
446
+ }
447
+ /** Avoid duplicate revocation work already pending, ready or acknowledged. */
448
+ hasRevocationQueuedOrSent(said) {
449
+ return this.cdb.rev.get([said]) !== null
450
+ || this.cdb.rack.get([said]) !== null
451
+ || this.hasReady(this.cdb.revk, said);
452
+ }
453
+ /** Require an accepted grant for this credential before recovering its revocation notification. */
454
+ hasAcceptedGrantForCredential(said) {
455
+ for (const [, grant] of this.hby.db.exns.getTopItemIter()) {
456
+ if (grant.route === IPEX_GRANT_ROUTE && credentialSaidFromGrant(grant) === said) {
457
+ return true;
458
+ }
459
+ }
460
+ return false;
461
+ }
462
+ /** Look up existing ready work by credential without consuming another delivery record. */
463
+ hasReady(db, said) {
464
+ for (const [keys] of db.getTopItemIter([said, ""])) {
465
+ if (keys[0] === said) {
466
+ return true;
467
+ }
468
+ }
469
+ return false;
470
+ }
471
+ /**
472
+ * Compare workflow age against the injected epoch clock.
473
+ * Ready work is attempted before expiry is checked after refusal. Expiry removes
474
+ * that ready row; it is not a permanent tombstone preventing later rediscovery.
475
+ *
476
+ * @param dater Persisted local workflow admission time encoded as a CESR Dater.
477
+ * @returns True only when elapsed injected wall time is strictly greater than timeoutMs.
478
+ */
479
+ expired(dater) {
480
+ return this.now() - new Date(dater.dts).getTime() > this.timeoutMs;
481
+ }
482
+ }
483
+ /** Read the existing legacy Reger observer state; this application does not interpret ACDC v2 registries. */
484
+ function credentialState(reger, creder) {
485
+ const regk = creder.regid;
486
+ const said = creder.said;
487
+ if (!regk || !said) {
488
+ return null;
489
+ }
490
+ const tever = reger.tevers.get(regk);
491
+ if (!isRecord(tever) || typeof tever.vcState !== "function") {
492
+ return null;
493
+ }
494
+ return tever.vcState(said);
495
+ }
496
+ /**
497
+ * Encode a local workflow timestamp from the same epoch-millisecond source used for expiry.
498
+ *
499
+ * @param now Local epoch milliseconds from the injected verifier clock.
500
+ * @returns CESR Dater used for durable workflow admission, not the signed protocol timestamp.
501
+ */
502
+ function nowDater(now) {
503
+ return new Dater({ dts: makeNowIso8601(new Date(now)) });
504
+ }
505
+ /** Refuse absent signed identifiers before writing workflow coordinates. */
506
+ function requireString(value, label) {
507
+ if (!value) {
508
+ throw new ValidationError(`Missing ${label}.`);
509
+ }
510
+ return value;
511
+ }
512
+ /** Narrow structural provider/attribute data at the public protocol boundary. */
513
+ function isRecord(value) {
514
+ return typeof value === "object" && value !== null && !Array.isArray(value);
515
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Sally-like verifier communication cue database.
3
+ *
4
+ * These application records remain outside protocol Baser/Reger evidence.
5
+ * ack/rack mark local HTTP success, not business receipts or KERI acknowledgements.
6
+ * This is Sally-inspired workflow state, not a claim of exact Sally storage parity.
7
+ */
8
+ import { Dater, Prefixer, SerderACDC } from "cesr-ts";
9
+ import { CesrSuber, LMDBer, SerderSuber } from "keri-ts/db";
10
+ import { DatabaseNotOpenError } from "keri-ts/runtime";
11
+ /** Durable local workflow records for pending, ready and delivered webhook work. */
12
+ export class VerifierCueBaser extends LMDBer {
13
+ constructor(options = {}) {
14
+ const compat = options.compat ?? false;
15
+ super(options, {
16
+ tailDirPath: VerifierCueBaser.TailDirPath,
17
+ cleanTailDirPath: "keri/clean/verifier",
18
+ altTailDirPath: compat ? VerifierCueBaser.CompatAltTailDirPath : VerifierCueBaser.AltTailDirPath,
19
+ altCleanTailDirPath: compat ? ".keri/clean/verifier" : ".tufa/clean/verifier",
20
+ tempPrefix: VerifierCueBaser.TempPrefix,
21
+ });
22
+ Object.defineProperty(this, "snd", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: void 0
27
+ });
28
+ Object.defineProperty(this, "iss", {
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true,
32
+ value: void 0
33
+ });
34
+ Object.defineProperty(this, "rev", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: void 0
39
+ });
40
+ Object.defineProperty(this, "recv", {
41
+ enumerable: true,
42
+ configurable: true,
43
+ writable: true,
44
+ value: void 0
45
+ });
46
+ Object.defineProperty(this, "revk", {
47
+ enumerable: true,
48
+ configurable: true,
49
+ writable: true,
50
+ value: void 0
51
+ });
52
+ Object.defineProperty(this, "ack", {
53
+ enumerable: true,
54
+ configurable: true,
55
+ writable: true,
56
+ value: void 0
57
+ });
58
+ Object.defineProperty(this, "rack", {
59
+ enumerable: true,
60
+ configurable: true,
61
+ writable: true,
62
+ value: void 0
63
+ });
64
+ }
65
+ /** Open ordinary LMDB storage and bind this application's typed tables. */
66
+ *reopen(options = {}) {
67
+ const opened = yield* super.reopen(options);
68
+ if (!opened) {
69
+ return false;
70
+ }
71
+ this.snd = new CesrSuber(this, { subkey: "snd.", ctor: Prefixer });
72
+ this.iss = new CesrSuber(this, { subkey: "iss.", ctor: Dater });
73
+ this.rev = new CesrSuber(this, { subkey: "rev.", ctor: Dater });
74
+ this.recv = new SerderSuber(this, { subkey: "recv", ctor: SerderACDC });
75
+ this.revk = new SerderSuber(this, { subkey: "revk", ctor: SerderACDC });
76
+ this.ack = new SerderSuber(this, { subkey: "ack", ctor: SerderACDC });
77
+ this.rack = new SerderSuber(this, { subkey: "rack", ctor: SerderACDC });
78
+ return true;
79
+ }
80
+ /** Clear workflow records, including the existing local delivered markers. */
81
+ clearEscrows() {
82
+ this.iss.trim();
83
+ this.rev.trim();
84
+ this.recv.trim();
85
+ this.revk.trim();
86
+ this.ack.trim();
87
+ this.rack.trim();
88
+ }
89
+ /** Snapshot application row counts for command output, never protocol acceptance. */
90
+ getCounts() {
91
+ return {
92
+ senders: this.snd.cntAll(),
93
+ iss: this.iss.cntAll(),
94
+ rev: this.rev.cntAll(),
95
+ recv: this.recv.cntAll(),
96
+ revk: this.revk.cntAll(),
97
+ ack: this.ack.cntAll(),
98
+ rack: this.rack.cntAll(),
99
+ };
100
+ }
101
+ }
102
+ Object.defineProperty(VerifierCueBaser, "TailDirPath", {
103
+ enumerable: true,
104
+ configurable: true,
105
+ writable: true,
106
+ value: "keri/verifier"
107
+ });
108
+ Object.defineProperty(VerifierCueBaser, "AltTailDirPath", {
109
+ enumerable: true,
110
+ configurable: true,
111
+ writable: true,
112
+ value: ".tufa/verifier"
113
+ });
114
+ Object.defineProperty(VerifierCueBaser, "CompatAltTailDirPath", {
115
+ enumerable: true,
116
+ configurable: true,
117
+ writable: true,
118
+ value: ".keri/verifier"
119
+ });
120
+ Object.defineProperty(VerifierCueBaser, "TempPrefix", {
121
+ enumerable: true,
122
+ configurable: true,
123
+ writable: true,
124
+ value: "keri_verifier_"
125
+ });
126
+ /** Open a verifier cue sidecar and return the ready-to-use databaser. */
127
+ export function* createVerifierCueBaser(options = {}) {
128
+ const cdb = new VerifierCueBaser(options);
129
+ const opened = yield* cdb.reopen(options);
130
+ if (!opened) {
131
+ throw new DatabaseNotOpenError("Failed to open VerifierCueBaser");
132
+ }
133
+ return cdb;
134
+ }
@@ -0,0 +1,45 @@
1
+ /** Return a business refusal reason without modifying accepted protocol evidence. */
2
+ export function validateCredential(creder, validators, context, requireKnownSchemas) {
3
+ const schema = credentialSchema(creder);
4
+ if (!schema) {
5
+ return "missing credential schema";
6
+ }
7
+ const validator = validators[schema];
8
+ if (!validator) {
9
+ return requireKnownSchemas ? `unsupported credential schema ${schema}` : null;
10
+ }
11
+ const result = validator(creder, context);
12
+ if (typeof result === "string") {
13
+ return result;
14
+ }
15
+ return result === false ? `credential ${creder.said ?? "<unknown>"} failed business validation` : null;
16
+ }
17
+ /** Build validators from a local JSON config object. */
18
+ export function validatorsFromVerifierConfig(config) {
19
+ if (!isRecord(config) || !isRecord(config.schemas)) {
20
+ return {};
21
+ }
22
+ const validators = {};
23
+ for (const [schema, policy] of Object.entries(config.schemas)) {
24
+ if (!isRecord(policy)) {
25
+ continue;
26
+ }
27
+ validators[schema] = (creder) => {
28
+ const issuer = typeof policy.issuer === "string" ? policy.issuer : "";
29
+ if (issuer && creder.issuer !== issuer) {
30
+ return `expected issuer ${issuer}, got ${creder.issuer ?? "<missing>"}`;
31
+ }
32
+ return true;
33
+ };
34
+ }
35
+ return validators;
36
+ }
37
+ /** Match the application's supported schema-SAID projection for policy and webhook consumers. */
38
+ export function credentialSchema(creder) {
39
+ const schema = creder.schema;
40
+ return typeof schema === "string" ? schema : isRecord(schema) && typeof schema.$id === "string" ? schema.$id : "";
41
+ }
42
+ /** Narrow operator JSON mappings without imposing protocol or schema validation here. */
43
+ function isRecord(value) {
44
+ return typeof value === "object" && value !== null && !Array.isArray(value);
45
+ }