@teamscala/service-boot 0.23.481 → 0.23.493

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.
Files changed (45) hide show
  1. package/package.json +1 -261
  2. package/src/agreement-gate/signing-session.ts +1 -1
  3. package/src/build-client-assets/styles-build.ts +11 -1
  4. package/src/mount-orpc-router.ts +1 -1
  5. package/src/register-page-server-client.ts +1 -15
  6. package/src/register-ws-routes.ts +1 -1
  7. package/src/fn-routers/agreement-certificate.ts +0 -224
  8. package/src/fn-routers/agreement-completion-email.ts +0 -114
  9. package/src/fn-routers/agreement-send-router.ts +0 -303
  10. package/src/fn-routers/agreement-sign-side-effects.ts +0 -192
  11. package/src/fn-routers/agreement-signing-evidence.ts +0 -105
  12. package/src/fn-routers/assessment-report-data.test.ts +0 -404
  13. package/src/fn-routers/assessment-report-data.ts +0 -485
  14. package/src/fn-routers/assessment-report-email.ts +0 -256
  15. package/src/fn-routers/assessment-report-pair.test.ts +0 -136
  16. package/src/fn-routers/assessment-report-pair.ts +0 -155
  17. package/src/fn-routers/assessment-research-report-data.test.ts +0 -233
  18. package/src/fn-routers/assessment-research-report-data.ts +0 -424
  19. package/src/fn-routers/assessment-scoring.ts +0 -105
  20. package/src/fn-routers/auth-router.ts +0 -65
  21. package/src/fn-routers/chat-session-router-shared.ts +0 -122
  22. package/src/fn-routers/chat-session-router.ts +0 -356
  23. package/src/fn-routers/conversation-ownership.ts +0 -24
  24. package/src/fn-routers/email-defaults.ts +0 -28
  25. package/src/fn-routers/email-link-ttl.ts +0 -28
  26. package/src/fn-routers/index.ts +0 -76
  27. package/src/fn-routers/linkedin-identity.test.ts +0 -27
  28. package/src/fn-routers/linkedin-identity.ts +0 -36
  29. package/src/fn-routers/messaging-whatsapp-router.test.ts +0 -121
  30. package/src/fn-routers/messaging-whatsapp-router.ts +0 -200
  31. package/src/fn-routers/panel-data-router.ts +0 -104
  32. package/src/fn-routers/persist-chat-messages.test.ts +0 -86
  33. package/src/fn-routers/persist-chat-messages.ts +0 -98
  34. package/src/fn-routers/proposal-send-router.ts +0 -207
  35. package/src/fn-routers/public-agreement-router.ts +0 -407
  36. package/src/fn-routers/public-assessment-router.test.ts +0 -437
  37. package/src/fn-routers/public-assessment-router.ts +0 -268
  38. package/src/fn-routers/public-gpt-session-router.ts +0 -378
  39. package/src/fn-routers/resend-client.ts +0 -39
  40. package/src/fn-routers/sign-in-flow-router.ts +0 -196
  41. package/src/fn-routers/user-preferences-router.ts +0 -130
  42. package/src/fn-routers/work-items-router.ts +0 -342
  43. package/src/page-server-orpc/audit-revert-router.ts +0 -102
  44. package/src/page-server-orpc/puck-pages-router.ts +0 -170
  45. package/src/page-server-orpc/user-preferences-router.ts +0 -132
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "name": "@teamscala/service-boot",
8
8
  "type": "module",
9
- "version": "0.23.481",
9
+ "version": "0.23.493",
10
10
  "sideEffects": false,
11
11
  "imports": {
12
12
  "#service-boot/*": "./src/*",
@@ -380,242 +380,6 @@
380
380
  "types": "./src/expand-roster.d.ts",
381
381
  "default": "./src/expand-roster.ts"
382
382
  },
383
- "./fn-routers/agreement-certificate": {
384
- "types": "./src/fn-routers/agreement-certificate.d.ts",
385
- "default": "./src/fn-routers/agreement-certificate.ts"
386
- },
387
- "./fn-routers/agreement-certificate.ts": {
388
- "types": "./src/fn-routers/agreement-certificate.d.ts",
389
- "default": "./src/fn-routers/agreement-certificate.ts"
390
- },
391
- "./fn-routers/agreement-completion-email": {
392
- "types": "./src/fn-routers/agreement-completion-email.d.ts",
393
- "default": "./src/fn-routers/agreement-completion-email.ts"
394
- },
395
- "./fn-routers/agreement-completion-email.ts": {
396
- "types": "./src/fn-routers/agreement-completion-email.d.ts",
397
- "default": "./src/fn-routers/agreement-completion-email.ts"
398
- },
399
- "./fn-routers/agreement-send-router": {
400
- "types": "./src/fn-routers/agreement-send-router.d.ts",
401
- "default": "./src/fn-routers/agreement-send-router.ts"
402
- },
403
- "./fn-routers/agreement-send-router.ts": {
404
- "types": "./src/fn-routers/agreement-send-router.d.ts",
405
- "default": "./src/fn-routers/agreement-send-router.ts"
406
- },
407
- "./fn-routers/agreement-sign-side-effects": {
408
- "types": "./src/fn-routers/agreement-sign-side-effects.d.ts",
409
- "default": "./src/fn-routers/agreement-sign-side-effects.ts"
410
- },
411
- "./fn-routers/agreement-sign-side-effects.ts": {
412
- "types": "./src/fn-routers/agreement-sign-side-effects.d.ts",
413
- "default": "./src/fn-routers/agreement-sign-side-effects.ts"
414
- },
415
- "./fn-routers/agreement-signing-evidence": {
416
- "types": "./src/fn-routers/agreement-signing-evidence.d.ts",
417
- "default": "./src/fn-routers/agreement-signing-evidence.ts"
418
- },
419
- "./fn-routers/agreement-signing-evidence.ts": {
420
- "types": "./src/fn-routers/agreement-signing-evidence.d.ts",
421
- "default": "./src/fn-routers/agreement-signing-evidence.ts"
422
- },
423
- "./fn-routers/assessment-report-data": {
424
- "types": "./src/fn-routers/assessment-report-data.d.ts",
425
- "default": "./src/fn-routers/assessment-report-data.ts"
426
- },
427
- "./fn-routers/assessment-report-data.ts": {
428
- "types": "./src/fn-routers/assessment-report-data.d.ts",
429
- "default": "./src/fn-routers/assessment-report-data.ts"
430
- },
431
- "./fn-routers/assessment-report-email": {
432
- "types": "./src/fn-routers/assessment-report-email.d.ts",
433
- "default": "./src/fn-routers/assessment-report-email.ts"
434
- },
435
- "./fn-routers/assessment-report-email.ts": {
436
- "types": "./src/fn-routers/assessment-report-email.d.ts",
437
- "default": "./src/fn-routers/assessment-report-email.ts"
438
- },
439
- "./fn-routers/assessment-report-pair": {
440
- "types": "./src/fn-routers/assessment-report-pair.d.ts",
441
- "default": "./src/fn-routers/assessment-report-pair.ts"
442
- },
443
- "./fn-routers/assessment-report-pair.ts": {
444
- "types": "./src/fn-routers/assessment-report-pair.d.ts",
445
- "default": "./src/fn-routers/assessment-report-pair.ts"
446
- },
447
- "./fn-routers/assessment-research-report-data": {
448
- "types": "./src/fn-routers/assessment-research-report-data.d.ts",
449
- "default": "./src/fn-routers/assessment-research-report-data.ts"
450
- },
451
- "./fn-routers/assessment-research-report-data.ts": {
452
- "types": "./src/fn-routers/assessment-research-report-data.d.ts",
453
- "default": "./src/fn-routers/assessment-research-report-data.ts"
454
- },
455
- "./fn-routers/assessment-scoring": {
456
- "types": "./src/fn-routers/assessment-scoring.d.ts",
457
- "default": "./src/fn-routers/assessment-scoring.ts"
458
- },
459
- "./fn-routers/assessment-scoring.ts": {
460
- "types": "./src/fn-routers/assessment-scoring.d.ts",
461
- "default": "./src/fn-routers/assessment-scoring.ts"
462
- },
463
- "./fn-routers/auth-router": {
464
- "types": "./src/fn-routers/auth-router.d.ts",
465
- "default": "./src/fn-routers/auth-router.ts"
466
- },
467
- "./fn-routers/auth-router.ts": {
468
- "types": "./src/fn-routers/auth-router.d.ts",
469
- "default": "./src/fn-routers/auth-router.ts"
470
- },
471
- "./fn-routers/chat-session-router": {
472
- "types": "./src/fn-routers/chat-session-router.d.ts",
473
- "default": "./src/fn-routers/chat-session-router.ts"
474
- },
475
- "./fn-routers/chat-session-router.ts": {
476
- "types": "./src/fn-routers/chat-session-router.d.ts",
477
- "default": "./src/fn-routers/chat-session-router.ts"
478
- },
479
- "./fn-routers/chat-session-router-shared": {
480
- "types": "./src/fn-routers/chat-session-router-shared.d.ts",
481
- "default": "./src/fn-routers/chat-session-router-shared.ts"
482
- },
483
- "./fn-routers/chat-session-router-shared.ts": {
484
- "types": "./src/fn-routers/chat-session-router-shared.d.ts",
485
- "default": "./src/fn-routers/chat-session-router-shared.ts"
486
- },
487
- "./fn-routers/conversation-ownership": {
488
- "types": "./src/fn-routers/conversation-ownership.d.ts",
489
- "default": "./src/fn-routers/conversation-ownership.ts"
490
- },
491
- "./fn-routers/conversation-ownership.ts": {
492
- "types": "./src/fn-routers/conversation-ownership.d.ts",
493
- "default": "./src/fn-routers/conversation-ownership.ts"
494
- },
495
- "./fn-routers/email-defaults": {
496
- "types": "./src/fn-routers/email-defaults.d.ts",
497
- "default": "./src/fn-routers/email-defaults.ts"
498
- },
499
- "./fn-routers/email-defaults.ts": {
500
- "types": "./src/fn-routers/email-defaults.d.ts",
501
- "default": "./src/fn-routers/email-defaults.ts"
502
- },
503
- "./fn-routers/email-link-ttl": {
504
- "types": "./src/fn-routers/email-link-ttl.d.ts",
505
- "default": "./src/fn-routers/email-link-ttl.ts"
506
- },
507
- "./fn-routers/email-link-ttl.ts": {
508
- "types": "./src/fn-routers/email-link-ttl.d.ts",
509
- "default": "./src/fn-routers/email-link-ttl.ts"
510
- },
511
- "./fn-routers/index": {
512
- "types": "./src/fn-routers/index.d.ts",
513
- "default": "./src/fn-routers/index.ts"
514
- },
515
- "./fn-routers/index.ts": {
516
- "types": "./src/fn-routers/index.d.ts",
517
- "default": "./src/fn-routers/index.ts"
518
- },
519
- "./fn-routers": {
520
- "types": "./src/fn-routers/index.d.ts",
521
- "default": "./src/fn-routers/index.ts"
522
- },
523
- "./fn-routers/linkedin-identity": {
524
- "types": "./src/fn-routers/linkedin-identity.d.ts",
525
- "default": "./src/fn-routers/linkedin-identity.ts"
526
- },
527
- "./fn-routers/linkedin-identity.ts": {
528
- "types": "./src/fn-routers/linkedin-identity.d.ts",
529
- "default": "./src/fn-routers/linkedin-identity.ts"
530
- },
531
- "./fn-routers/messaging-whatsapp-router": {
532
- "types": "./src/fn-routers/messaging-whatsapp-router.d.ts",
533
- "default": "./src/fn-routers/messaging-whatsapp-router.ts"
534
- },
535
- "./fn-routers/messaging-whatsapp-router.ts": {
536
- "types": "./src/fn-routers/messaging-whatsapp-router.d.ts",
537
- "default": "./src/fn-routers/messaging-whatsapp-router.ts"
538
- },
539
- "./fn-routers/panel-data-router": {
540
- "types": "./src/fn-routers/panel-data-router.d.ts",
541
- "default": "./src/fn-routers/panel-data-router.ts"
542
- },
543
- "./fn-routers/panel-data-router.ts": {
544
- "types": "./src/fn-routers/panel-data-router.d.ts",
545
- "default": "./src/fn-routers/panel-data-router.ts"
546
- },
547
- "./fn-routers/persist-chat-messages": {
548
- "types": "./src/fn-routers/persist-chat-messages.d.ts",
549
- "default": "./src/fn-routers/persist-chat-messages.ts"
550
- },
551
- "./fn-routers/persist-chat-messages.ts": {
552
- "types": "./src/fn-routers/persist-chat-messages.d.ts",
553
- "default": "./src/fn-routers/persist-chat-messages.ts"
554
- },
555
- "./fn-routers/proposal-send-router": {
556
- "types": "./src/fn-routers/proposal-send-router.d.ts",
557
- "default": "./src/fn-routers/proposal-send-router.ts"
558
- },
559
- "./fn-routers/proposal-send-router.ts": {
560
- "types": "./src/fn-routers/proposal-send-router.d.ts",
561
- "default": "./src/fn-routers/proposal-send-router.ts"
562
- },
563
- "./fn-routers/public-agreement-router": {
564
- "types": "./src/fn-routers/public-agreement-router.d.ts",
565
- "default": "./src/fn-routers/public-agreement-router.ts"
566
- },
567
- "./fn-routers/public-agreement-router.ts": {
568
- "types": "./src/fn-routers/public-agreement-router.d.ts",
569
- "default": "./src/fn-routers/public-agreement-router.ts"
570
- },
571
- "./fn-routers/public-assessment-router": {
572
- "types": "./src/fn-routers/public-assessment-router.d.ts",
573
- "default": "./src/fn-routers/public-assessment-router.ts"
574
- },
575
- "./fn-routers/public-assessment-router.ts": {
576
- "types": "./src/fn-routers/public-assessment-router.d.ts",
577
- "default": "./src/fn-routers/public-assessment-router.ts"
578
- },
579
- "./fn-routers/public-gpt-session-router": {
580
- "types": "./src/fn-routers/public-gpt-session-router.d.ts",
581
- "default": "./src/fn-routers/public-gpt-session-router.ts"
582
- },
583
- "./fn-routers/public-gpt-session-router.ts": {
584
- "types": "./src/fn-routers/public-gpt-session-router.d.ts",
585
- "default": "./src/fn-routers/public-gpt-session-router.ts"
586
- },
587
- "./fn-routers/resend-client": {
588
- "types": "./src/fn-routers/resend-client.d.ts",
589
- "default": "./src/fn-routers/resend-client.ts"
590
- },
591
- "./fn-routers/resend-client.ts": {
592
- "types": "./src/fn-routers/resend-client.d.ts",
593
- "default": "./src/fn-routers/resend-client.ts"
594
- },
595
- "./fn-routers/sign-in-flow-router": {
596
- "types": "./src/fn-routers/sign-in-flow-router.d.ts",
597
- "default": "./src/fn-routers/sign-in-flow-router.ts"
598
- },
599
- "./fn-routers/sign-in-flow-router.ts": {
600
- "types": "./src/fn-routers/sign-in-flow-router.d.ts",
601
- "default": "./src/fn-routers/sign-in-flow-router.ts"
602
- },
603
- "./fn-routers/user-preferences-router": {
604
- "types": "./src/fn-routers/user-preferences-router.d.ts",
605
- "default": "./src/fn-routers/user-preferences-router.ts"
606
- },
607
- "./fn-routers/user-preferences-router.ts": {
608
- "types": "./src/fn-routers/user-preferences-router.d.ts",
609
- "default": "./src/fn-routers/user-preferences-router.ts"
610
- },
611
- "./fn-routers/work-items-router": {
612
- "types": "./src/fn-routers/work-items-router.d.ts",
613
- "default": "./src/fn-routers/work-items-router.ts"
614
- },
615
- "./fn-routers/work-items-router.ts": {
616
- "types": "./src/fn-routers/work-items-router.d.ts",
617
- "default": "./src/fn-routers/work-items-router.ts"
618
- },
619
383
  "./gate-ttls": {
620
384
  "types": "./src/gate-ttls.d.ts",
621
385
  "default": "./src/gate-ttls.ts"
@@ -888,14 +652,6 @@
888
652
  "types": "./src/page-route-shell-types.d.ts",
889
653
  "default": "./src/page-route-shell-types.ts"
890
654
  },
891
- "./page-server-orpc/audit-revert-router": {
892
- "types": "./src/page-server-orpc/audit-revert-router.d.ts",
893
- "default": "./src/page-server-orpc/audit-revert-router.ts"
894
- },
895
- "./page-server-orpc/audit-revert-router.ts": {
896
- "types": "./src/page-server-orpc/audit-revert-router.d.ts",
897
- "default": "./src/page-server-orpc/audit-revert-router.ts"
898
- },
899
655
  "./page-server-orpc/hub-mcp-client": {
900
656
  "types": "./src/page-server-orpc/hub-mcp-client.d.ts",
901
657
  "default": "./src/page-server-orpc/hub-mcp-client.ts"
@@ -904,14 +660,6 @@
904
660
  "types": "./src/page-server-orpc/hub-mcp-client.d.ts",
905
661
  "default": "./src/page-server-orpc/hub-mcp-client.ts"
906
662
  },
907
- "./page-server-orpc/puck-pages-router": {
908
- "types": "./src/page-server-orpc/puck-pages-router.d.ts",
909
- "default": "./src/page-server-orpc/puck-pages-router.ts"
910
- },
911
- "./page-server-orpc/puck-pages-router.ts": {
912
- "types": "./src/page-server-orpc/puck-pages-router.d.ts",
913
- "default": "./src/page-server-orpc/puck-pages-router.ts"
914
- },
915
663
  "./page-server-orpc/server-client": {
916
664
  "types": "./src/page-server-orpc/server-client.d.ts",
917
665
  "default": "./src/page-server-orpc/server-client.ts"
@@ -920,14 +668,6 @@
920
668
  "types": "./src/page-server-orpc/server-client.d.ts",
921
669
  "default": "./src/page-server-orpc/server-client.ts"
922
670
  },
923
- "./page-server-orpc/user-preferences-router": {
924
- "types": "./src/page-server-orpc/user-preferences-router.d.ts",
925
- "default": "./src/page-server-orpc/user-preferences-router.ts"
926
- },
927
- "./page-server-orpc/user-preferences-router.ts": {
928
- "types": "./src/page-server-orpc/user-preferences-router.d.ts",
929
- "default": "./src/page-server-orpc/user-preferences-router.ts"
930
- },
931
671
  "./pg-boss-handler-registry": {
932
672
  "types": "./src/pg-boss-handler-registry.d.ts",
933
673
  "default": "./src/pg-boss-handler-registry.ts"
@@ -20,7 +20,7 @@ import {
20
20
  import {
21
21
  buildCertificateData,
22
22
  type CertificateData,
23
- } from "../fn-routers/agreement-certificate.ts";
23
+ } from "@teamscala/agreement-generation/agreement-certificate";
24
24
  import { findFirstAgreement } from "./agreement-lookup.ts";
25
25
  import { resolveAgreementViewerDirectory } from "./resolution-caches.ts";
26
26
 
@@ -213,8 +213,18 @@ export async function buildTailwindCss(
213
213
  await mkdir(outDir, { recursive: true });
214
214
 
215
215
  const cssSource = await Bun.file(entry).text();
216
+ // base MUST be the ENTRY FILE's directory (src/), never the service root.
217
+ // The generated app.css authors its @source dirs relative to itself
218
+ // ("../node_modules/@teamscala/<pkg>/src"); Tailwind 4.3 resolves @source
219
+ // against THIS base, so passing the service root aimed every scan one
220
+ // level too high (workspace/node_modules — empty) and the candidate set
221
+ // collapsed to zero: build([]) strips every Tailwind utility and every
222
+ // kind:ui service renders with naked buttons/controls while the
223
+ // stylesheet still loads with its full package-rule payload (measured
224
+ // 2026-09-05, operator-visible fleet-wide). Package-specifier @imports are
225
+ // unaffected — they resolve through loadStylesheet's Bun.resolve walk.
216
226
  const compiler = await compile(cssSource, {
217
- base: cwd,
227
+ base: join(cwd, "src"),
218
228
  async loadStylesheet(id, base) {
219
229
  return bunResolveStylesheet(id, base, cwd);
220
230
  },
@@ -6,7 +6,7 @@
6
6
  * Mounts the composed ORPC app router (generated Prisma CRUD + custom fn routers)
7
7
  * at /api/rpc/* over HTTP via @orpc/server/fetch's RPCHandler.
8
8
  * Uses getAppRouter() from @teamscala/orpc/app-router which merges generated
9
- * routers with custom routers registered via registerCustomRouter().
9
+ * routers plus the row-composed fn namespace (orpc-bundle from api_route rows).
10
10
  */
11
11
 
12
12
  import { join } from "node:path";
@@ -99,21 +99,7 @@ export async function registerPageServerClient(
99
99
  );
100
100
  }
101
101
 
102
- const { puckPagesRouter } = await import(
103
- "@teamscala/service-boot/page-server-orpc/puck-pages-router"
104
- );
105
- const { userPreferencesRouter } = await import(
106
- "@teamscala/service-boot/page-server-orpc/user-preferences-router"
107
- );
108
- const { auditRevertRouter } = await import(
109
- "@teamscala/service-boot/page-server-orpc/audit-revert-router"
110
- );
111
- const composed = {
112
- ...((appRouter as Record<string, unknown> | null) ?? {}),
113
- puckPages: puckPagesRouter,
114
- userPreferences: userPreferencesRouter,
115
- auditRevert: auditRevertRouter,
116
- };
102
+ const composed = (appRouter as Record<string, unknown> | null) ?? {};
117
103
  const { loadRegistryConfig } = await import(
118
104
  "@teamscala/db/registry/load-config"
119
105
  );
@@ -6,7 +6,7 @@
6
6
  * Registers each `service_install.websocket_routes[]` entry as a Hono WS route.
7
7
  * The ORPC WS (`/api/rpc/ws`) is mounted by the canonical primitive
8
8
  * `mountOrpcWs` (@teamscala/orpc/ws/server) over the FULLY-COMPOSED app router —
9
- * this runs AFTER registerFnRouters, so the eager RPCHandler snapshots every
9
+ * this runs AFTER the app router composition, so the eager RPCHandler snapshots every
10
10
  * namespace, and any `output: eventIterator(...)` procedure dispatches (rule
11
11
  * `orpc-ws-via-mount-orpc-ws`). It is NOT a server_package WsRouteHandler — it
12
12
  * is exempt from the legacy invariant below.
@@ -1,224 +0,0 @@
1
- /**
2
- * @system service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The Certificate of Completion for an executed agreement: DB-driven display
7
- * copy (config/certificate-of-completion), the CertificateData shape, and
8
- * the party-gated builder.
9
- *
10
- * Split out of fn-routers/public-agreement-router.ts (853 lines) —
11
- * byte-for-byte move; import paths adjusted only.
12
- */
13
-
14
- import * as v from "valibot";
15
- import { queryRows } from "@teamscala/db/query-rows";
16
- import { loadRegistryConfig } from "@teamscala/db/registry/load-config";
17
- import {
18
- parseAgreementParties as parseParties,
19
- } from "@teamscala/document-signing/agreement-parties";
20
- import { parseEvidence } from "./agreement-signing-evidence.ts";
21
-
22
- /** The Certificate of Completion display copy. DB-driven via the
23
- * `config/certificate-of-completion` registry row — mirrors the agreement gate's
24
- * `config/content-gate-agreement` pattern (same signing surface), so an operator
25
- * can rebrand/reword the certificate via `registry_edit` with no deploy; the
26
- * hardcoded defaults are the fallback. Generic structural data-column labels
27
- * (Signer/Role/Envelope ID/…) stay in the component, exactly as the gate keeps
28
- * its structural markup in code. */
29
- export interface CertificateCopy {
30
- title: string;
31
- signersHeading: string;
32
- sealHeading: string;
33
- sealNote: string;
34
- footer: string;
35
- badgeCompleted: string;
36
- badgeInProgress: string;
37
- /** Audit-table column labels — DB-driven so rewording is a registry_edit.
38
- * Each labels BOTH the <th> header AND the stacked-row data-label on mobile
39
- * (one source, kept in sync), never a second hardcoded copy. */
40
- colSigner: string;
41
- colRole: string;
42
- colSigned: string;
43
- colIp: string;
44
- colAuth: string;
45
- }
46
-
47
- const CERTIFICATE_COPY_DEFAULTS: CertificateCopy = {
48
- title: "CERTIFICATE OF COMPLETION",
49
- signersHeading: "Signers",
50
- sealHeading: "Tamper-evident seal",
51
- sealNote:
52
- "{{hashAlgo}} hash of the executed document + signed roster, sealed {{hashedAt}}. Re-hashing the stored document and comparing to this value proves it has not been altered since signing.",
53
- footer:
54
- "This certificate is a record of the electronic signing of the above agreement, produced from the platform's permanent audit trail. Electronic signatures are legally recognised under the ESIGN Act and UETA (US) and eIDAS (EU).",
55
- badgeCompleted: "Completed",
56
- badgeInProgress: "In progress",
57
- colSigner: "Signer",
58
- colRole: "Role",
59
- colSigned: "Signed (UTC)",
60
- colIp: "IP address",
61
- colAuth: "Auth",
62
- };
63
-
64
- const CertificateCopySchema = v.object({
65
- title: v.optional(v.string(), CERTIFICATE_COPY_DEFAULTS.title),
66
- signersHeading: v.optional(
67
- v.string(),
68
- CERTIFICATE_COPY_DEFAULTS.signersHeading,
69
- ),
70
- sealHeading: v.optional(v.string(), CERTIFICATE_COPY_DEFAULTS.sealHeading),
71
- sealNote: v.optional(v.string(), CERTIFICATE_COPY_DEFAULTS.sealNote),
72
- footer: v.optional(v.string(), CERTIFICATE_COPY_DEFAULTS.footer),
73
- badgeCompleted: v.optional(
74
- v.string(),
75
- CERTIFICATE_COPY_DEFAULTS.badgeCompleted,
76
- ),
77
- badgeInProgress: v.optional(
78
- v.string(),
79
- CERTIFICATE_COPY_DEFAULTS.badgeInProgress,
80
- ),
81
- colSigner: v.optional(v.string(), CERTIFICATE_COPY_DEFAULTS.colSigner),
82
- colRole: v.optional(v.string(), CERTIFICATE_COPY_DEFAULTS.colRole),
83
- colSigned: v.optional(v.string(), CERTIFICATE_COPY_DEFAULTS.colSigned),
84
- colIp: v.optional(v.string(), CERTIFICATE_COPY_DEFAULTS.colIp),
85
- colAuth: v.optional(v.string(), CERTIFICATE_COPY_DEFAULTS.colAuth),
86
- });
87
-
88
- async function loadCertificateCopy(): Promise<CertificateCopy | null> {
89
- try {
90
- return await loadRegistryConfig(
91
- "config",
92
- "certificate-of-completion",
93
- CertificateCopySchema,
94
- );
95
- } catch {
96
- // Absent signal, not a masking fallback (protective-layers-fail-explicitly):
97
- // null propagates to CertificateData.copy, and the component renders its
98
- // FALLBACK_COPY — same default values, but the absence is honest rather
99
- // than disguised as a successful config load.
100
- return null;
101
- }
102
- }
103
-
104
- /** The Certificate of Completion data shape — the court-style audit certificate.
105
- * One home: {@link buildCertificateData} is consumed by the SSR agreement
106
- * gate, so the signing block receives the server-seeded prop. */
107
- export interface CertificateData {
108
- ok: boolean;
109
- envelopeId?: string;
110
- documentName?: string | null;
111
- issuedAt?: string;
112
- complete?: boolean;
113
- createdAt?: string | null;
114
- sentAt?: string | null;
115
- viewedAt?: string | null;
116
- signedAt?: string | null;
117
- documentHash?: string | null;
118
- hashAlgo?: string | null;
119
- hashedAt?: string | null;
120
- parties?: {
121
- role: string;
122
- name: string | null;
123
- email: string | null;
124
- title: string | null;
125
- company: string | null;
126
- signedAt: string | null;
127
- ip: string | null;
128
- userAgent: string | null;
129
- authMethod: string | null;
130
- }[];
131
- /** DB-driven display copy (config/certificate-of-completion); null when the
132
- * config row is missing/broken — the component falls back to FALLBACK_COPY. */
133
- copy?: CertificateCopy | null;
134
- }
135
-
136
- /**
137
- * Build the Certificate of Completion data for an agreement, gated to a party
138
- * email — signer evidence (IP/device) is never exposed to a non-party. One data
139
- * home: the SSR agreement gate (`buildSigningSession` in agreement-gate.ts)
140
- * calls this. The read is
141
- * intentionally RLS-unscoped via the raw registry prisma — the certificate is
142
- * reached without a session (AG cookie only), which context.prisma's RLS would
143
- * otherwise treat as no-org and hide. Accepts either the agreement record id or
144
- * its page id (the UUID the signer sees in the URL is the page id).
145
- */
146
- export async function buildCertificateData(
147
- agreementId: string,
148
- email: string,
149
- ): Promise<CertificateData> {
150
- const id = agreementId.trim();
151
- const mail = email.trim().toLowerCase();
152
- if (!id || !mail) return { ok: false };
153
- const [agreement] = await queryRows<
154
- {
155
- id: string;
156
- name: string;
157
- created_at: Date | null;
158
- sent_at: Date | null;
159
- viewed_at: Date | null;
160
- signed_at: Date | null;
161
- signature_data: unknown;
162
- signer_email: string | null;
163
- signer_name: string | null;
164
- }
165
- >`
166
- SELECT id, name, created_at, sent_at, viewed_at, signed_at,
167
- signature_data, signer_email, signer_name
168
- FROM agreements
169
- WHERE (id = ${id} OR page_id = ${id})
170
- LIMIT 1`;
171
- if (!agreement) return { ok: false };
172
- const parties = parseParties(agreement.signature_data);
173
- const evidence = parseEvidence(agreement.signature_data);
174
- const isParty = parties
175
- ? parties.some((p) => p.email.trim().toLowerCase() === mail)
176
- : String(agreement.signer_email ?? "")
177
- .trim()
178
- .toLowerCase() === mail;
179
- if (!isParty) return { ok: false };
180
- const complete =
181
- !!agreement.signed_at || (parties?.every((p) => p.signedAt) ?? false);
182
- const iso = (d: Date | null | undefined) => d?.toISOString() ?? null;
183
- const copy = await loadCertificateCopy();
184
- return {
185
- ok: true,
186
- copy,
187
- envelopeId: agreement.id,
188
- documentName: agreement.name,
189
- issuedAt: new Date().toISOString(),
190
- complete,
191
- createdAt: iso(agreement.created_at),
192
- sentAt: iso(agreement.sent_at),
193
- viewedAt: iso(agreement.viewed_at),
194
- signedAt: iso(agreement.signed_at),
195
- documentHash: evidence?.documentHash ?? null,
196
- hashAlgo: evidence?.hashAlgo ?? null,
197
- hashedAt: evidence?.hashedAt ?? null,
198
- parties: parties
199
- ? parties.map((p) => ({
200
- role: p.key,
201
- name: p.name,
202
- email: p.email,
203
- title: p.title,
204
- company: p.company,
205
- signedAt: p.signedAt,
206
- ip: p.signedIp ?? null,
207
- userAgent: p.signedUserAgent ?? null,
208
- authMethod: p.authMethod ?? null,
209
- }))
210
- : [
211
- {
212
- role: "signer",
213
- name: agreement.signer_name,
214
- email: agreement.signer_email,
215
- title: null,
216
- company: null,
217
- signedAt: iso(agreement.signed_at),
218
- ip: null,
219
- userAgent: null,
220
- authMethod: "email-match",
221
- },
222
- ],
223
- };
224
- }