@teamscala/service-boot 0.23.467 → 0.23.468

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "name": "@teamscala/service-boot",
8
8
  "type": "module",
9
- "version": "0.23.467",
9
+ "version": "0.23.468",
10
10
  "sideEffects": false,
11
11
  "imports": {
12
12
  "#service-boot/*": "./src/*",
@@ -11,9 +11,19 @@
11
11
  * Off-the-shelf path (building-applications): typed ORPC procedure, not a
12
12
  * hand-written Hono handler; writes go through context.prisma inside the
13
13
  * handler (orpc-is-the-only-api) so the audit trigger sees the ORPC caller.
14
- * Multi-party model: signature_data holds a parties roster (JSON); each party
15
- * is gated by its own email and the agreement completes only when every party
16
- * has signed; single-signer falls back to signer_email/signer_name.
14
+ * ONE signing model, the DocuSign recipients shape: signature_data holds a
15
+ * parties roster (JSON), each party is gated by its own email, and the
16
+ * agreement completes only when every party has signed. A single signer is a
17
+ * roster of one -- there is no separate single-signer path.
18
+ *
19
+ * A legacy signer_email/signer_name fallback used to sit alongside this and was
20
+ * deleted 2026-09-03: measured across every agreement on the platform, 13 of 14
21
+ * carried a roster and ZERO used the fallback (the 14th was an unsent draft with
22
+ * no signature_data at all). It was never exercised, and it was not inert -- its
23
+ * write put a BARE STRING into signature_data, which would have destroyed the
24
+ * roster of any agreement that reached it. Keeping it also meant every change to
25
+ * this handler had to be made twice: the Prisma 8 timestamptz fix that made
26
+ * signing work at all had to be applied to both branches of one function.
17
27
  *
18
28
  * This module holds the three procedures (verify/sign/verifyDocument); the
19
29
  * supporting machinery lives as siblings (agreement-signing-evidence,
@@ -114,19 +124,9 @@ export const publicAgreementRouter = {
114
124
  };
115
125
  }
116
126
 
117
- // Legacy single-signer path.
118
- const matched =
119
- String(agreement.signer_email ?? "")
120
- .trim()
121
- .toLowerCase() === email;
122
- if (!matched) return { matched: false as const };
123
- return {
124
- matched: true,
125
- name: agreement.name,
126
- signerName: agreement.signer_name,
127
- signedAt: agreement.signed_at,
128
- accessToken: signAgreementAccessToken(agreementId, email, Date.now()),
129
- };
127
+ // No roster means the agreement was never prepared for signing. It is
128
+ // not viewable, and it is certainly not signable.
129
+ return { matched: false as const };
130
130
  }),
131
131
 
132
132
  // Commit the authed party's signature. Returns { ok, parties } so the block
@@ -309,47 +309,11 @@ export const publicAgreementRouter = {
309
309
  return result;
310
310
  }
311
311
 
312
- // Legacy single-signer path.
313
- const signerEmail = String(agreement.signer_email ?? "")
314
- .trim()
315
- .toLowerCase();
316
- if (!signerEmail || signerEmail !== email) {
317
- return {
318
- ok: false as const,
319
- error: "This email does not match the agreement's signer.",
320
- };
321
- }
322
- if (agreement.signed_at) {
323
- return {
324
- ok: true as const,
325
- alreadySigned: true,
326
- signedAt: agreement.signed_at,
327
- signerName: agreement.signer_name,
328
- };
329
- }
330
- const updated = await context.prisma.agreements.update({
331
- where: { id: agreementId },
332
- data: {
333
- signature_data: input.signatureData ?? agreement.signer_name ?? "",
334
- signed_at: nowInstant,
335
- viewed_at: nowInstant,
336
- signed_by: agreement.contact_id ?? null,
337
- ...(input.signerName ? { signer_name: input.signerName } : {}),
338
- },
339
- });
340
- // Every captured identity → CRM: connect this signer to client_database.
341
- await connectSignerToCrm(
342
- context.prisma,
343
- agreement,
344
- email,
345
- input.signerName ?? agreement.signer_name,
346
- input.company,
347
- );
312
+ // An agreement with no roster was never prepared for signing.
313
+ // Refuse rather than invent a signer.
348
314
  return {
349
- ok: true as const,
350
- signedAt: updated.signed_at,
351
- signerName: updated.signer_name,
352
- payUrl: await invoicePayUrl(agreementId, originFrom(context.headers)),
315
+ ok: false as const,
316
+ error: "This agreement has no signers recorded and cannot be signed.",
353
317
  };
354
318
  } catch (err) {
355
319
  log.error(