@company-semantics/contracts 28.0.0 → 29.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "28.0.0",
3
+ "version": "29.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,7 +8,7 @@ exports[`email render > auth.otp · Default 1`] = `
8
8
  <body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; margin: 0; padding: 0;">
9
9
  <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">Copy/paste code in login form:</p>
10
10
  <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; border-radius: 0; margin: 0 0 20px 0;">
11
- <tr><td style="padding: 20px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; letter-spacing: 4px;">
11
+ <tr><td style="padding: 20px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px; letter-spacing: 4px;">
12
12
  <b>&gt;&gt; 123456 &lt;&lt;</b>
13
13
  </td></tr>
14
14
  </table>
@@ -49,7 +49,7 @@ exports[`email render > auth.otp · Short expiry 1`] = `
49
49
  <body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; margin: 0; padding: 0;">
50
50
  <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">Copy/paste code in login form:</p>
51
51
  <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; border-radius: 0; margin: 0 0 20px 0;">
52
- <tr><td style="padding: 20px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; letter-spacing: 4px;">
52
+ <tr><td style="padding: 20px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px; letter-spacing: 4px;">
53
53
  <b>&gt;&gt; 902413 &lt;&lt;</b>
54
54
  </td></tr>
55
55
  </table>
@@ -90,7 +90,7 @@ exports[`email render > auth.otp · With request metadata 1`] = `
90
90
  <body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; margin: 0; padding: 0;">
91
91
  <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">Copy/paste code in login form:</p>
92
92
  <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; border-radius: 0; margin: 0 0 20px 0;">
93
- <tr><td style="padding: 20px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; letter-spacing: 4px;">
93
+ <tr><td style="padding: 20px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px; letter-spacing: 4px;">
94
94
  <b>&gt;&gt; 246810 &lt;&lt;</b>
95
95
  </td></tr>
96
96
  </table>
@@ -150,8 +150,8 @@ exports[`email render > chat.shared · Default 1`] = `
150
150
  </tr>
151
151
  </table>
152
152
  <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; margin: 0 0 16px 0; max-width: 320px; text-align: center;">⋮</p>
153
- <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 200px;">
154
- <tr><td style="padding: 12px 20px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px;">
153
+ <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 220px;">
154
+ <tr><td style="padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px;">
155
155
  <a href="https://app.companysemantics.ai/share/abc123" style="color: #0047FF; text-decoration: underline;"><b>&gt;&gt; SEE MORE &lt;&lt;</b></a>
156
156
  </td></tr>
157
157
  </table>
@@ -211,8 +211,8 @@ exports[`email render > chat.shared · Long preview 1`] = `
211
211
  </tr>
212
212
  </table>
213
213
  <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; margin: 0 0 16px 0; max-width: 320px; text-align: center;">⋮</p>
214
- <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 200px;">
215
- <tr><td style="padding: 12px 20px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px;">
214
+ <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 220px;">
215
+ <tr><td style="padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px;">
216
216
  <a href="https://app.companysemantics.ai/share/preview789" style="color: #0047FF; text-decoration: underline;"><b>&gt;&gt; SEE MORE &lt;&lt;</b></a>
217
217
  </td></tr>
218
218
  </table>
@@ -272,8 +272,8 @@ exports[`email render > chat.shared · Long title 1`] = `
272
272
  </tr>
273
273
  </table>
274
274
  <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; margin: 0 0 16px 0; max-width: 320px; text-align: center;">⋮</p>
275
- <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 200px;">
276
- <tr><td style="padding: 12px 20px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px;">
275
+ <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 220px;">
276
+ <tr><td style="padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px;">
277
277
  <a href="https://app.companysemantics.ai/share/long456" style="color: #0047FF; text-decoration: underline;"><b>&gt;&gt; SEE MORE &lt;&lt;</b></a>
278
278
  </td></tr>
279
279
  </table>
@@ -327,8 +327,8 @@ exports[`email render > chat.shared · No preview 1`] = `
327
327
  </tr>
328
328
  </table>
329
329
  <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; margin: 0 0 16px 0; max-width: 320px; text-align: center;">⋮</p>
330
- <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 200px;">
331
- <tr><td style="padding: 12px 20px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px;">
330
+ <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 220px;">
331
+ <tr><td style="padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px;">
332
332
  <a href="https://app.companysemantics.ai/share/xyz789" style="color: #0047FF; text-decoration: underline;"><b>&gt;&gt; SEE MORE &lt;&lt;</b></a>
333
333
  </td></tr>
334
334
  </table>
@@ -382,8 +382,8 @@ exports[`email render > chat.shared · Short 1`] = `
382
382
  </tr>
383
383
  </table>
384
384
  <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; margin: 0 0 16px 0; max-width: 320px; text-align: center;">⋮</p>
385
- <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 200px;">
386
- <tr><td style="padding: 12px 20px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px;">
385
+ <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 220px;">
386
+ <tr><td style="padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px;">
387
387
  <a href="https://app.companysemantics.ai/share/short123" style="color: #0047FF; text-decoration: underline;"><b>&gt;&gt; SEE MORE &lt;&lt;</b></a>
388
388
  </td></tr>
389
389
  </table>
@@ -662,8 +662,8 @@ exports[`email render > org.invite · Admin 1`] = `
662
662
  <head><meta charset="UTF-8"></head>
663
663
  <body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; margin: 0; padding: 0;">
664
664
  <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">Workspace invitation.</p>
665
- <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0;">
666
- <tr><td style="padding: 20px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px;">
665
+ <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 220px;">
666
+ <tr><td style="padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px;">
667
667
  <a href="https://app.companysemantics.ai/invite/abc123" style="color: #0047FF; text-decoration: underline;"><b>&gt;&gt; JOIN &lt;&lt;</b></a>
668
668
  </td></tr>
669
669
  </table>
@@ -706,8 +706,8 @@ exports[`email render > org.invite · Member 1`] = `
706
706
  <head><meta charset="UTF-8"></head>
707
707
  <body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; margin: 0; padding: 0;">
708
708
  <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">Workspace invitation.</p>
709
- <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0;">
710
- <tr><td style="padding: 20px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px;">
709
+ <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 220px;">
710
+ <tr><td style="padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px;">
711
711
  <a href="https://app.companysemantics.ai/invite/def456" style="color: #0047FF; text-decoration: underline;"><b>&gt;&gt; JOIN &lt;&lt;</b></a>
712
712
  </td></tr>
713
713
  </table>
@@ -745,6 +745,21 @@ Questions? Contact support@companysemantics.ai
745
745
 
746
746
  exports[`email render > org.ownership_transfer · No note 1`] = `
747
747
  {
748
+ "html": "<!DOCTYPE html>
749
+ <html lang="en">
750
+ <head><meta charset="UTF-8"></head>
751
+ <body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; margin: 0; padding: 0;">
752
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">You have been invited to become the owner of <b>&quot;Acme Corp&quot;</b>.</p>
753
+ <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 220px;">
754
+ <tr><td style="padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px;">
755
+ <a href="https://app.companysemantics.ai/owner-transfer/accept/tok456" style="color: #0047FF; text-decoration: underline;"><b>&gt;&gt; ACCEPT &lt;&lt;</b></a>
756
+ </td></tr>
757
+ </table>
758
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">Expires in: <b>14 days</b></p>
759
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">If you did not expect this invitation, you can safely ignore this email.</p>
760
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0;">Company Semantics<br><span style="color: #888;">Questions? Contact <a href="mailto:support@companysemantics.ai" style="color: #888;">support@companysemantics.ai</a></span></p>
761
+ </body>
762
+ </html>",
748
763
  "subject": "You've been invited to become a workspace owner",
749
764
  "text": "You have been invited to become the owner of "Acme Corp".
750
765
 
@@ -767,6 +782,31 @@ Questions? Contact support@companysemantics.ai
767
782
 
768
783
  exports[`email render > org.ownership_transfer · With note 1`] = `
769
784
  {
785
+ "html": "<!DOCTYPE html>
786
+ <html lang="en">
787
+ <head><meta charset="UTF-8"></head>
788
+ <body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; margin: 0; padding: 0;">
789
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">You have been invited to become the owner of <b>&quot;Acme Corp&quot;</b>.</p>
790
+ <table cellpadding="0" cellspacing="0" border="0" style="margin: 0 0 16px 0;">
791
+ <tr>
792
+ <td style="border: 1px solid #1a1a1a; border-radius: 12px 12px 0 12px; padding: 10px 14px; min-width: 280px; text-align: right; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; background: #f5f5f5;">Handing this over as I move to an advisory role.</td>
793
+ <td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; padding-left: 8px; vertical-align: bottom;">(•̀_ರ╮)</td>
794
+ </tr>
795
+ <tr>
796
+ <td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 11px; color: #888; text-align: right; padding-top: 4px;">Message from the current owner</td>
797
+ <td></td>
798
+ </tr>
799
+ </table>
800
+ <table cellpadding="0" cellspacing="0" border="0" style="border: 2px solid #1a1a1a; margin: 0 0 20px 0; max-width: 220px;">
801
+ <tr><td style="padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 16px;">
802
+ <a href="https://app.companysemantics.ai/owner-transfer/accept/tok123" style="color: #0047FF; text-decoration: underline;"><b>&gt;&gt; ACCEPT &lt;&lt;</b></a>
803
+ </td></tr>
804
+ </table>
805
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">Expires in: <b>14 days</b></p>
806
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">If you did not expect this invitation, you can safely ignore this email.</p>
807
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0;">Company Semantics<br><span style="color: #888;">Questions? Contact <a href="mailto:support@companysemantics.ai" style="color: #888;">support@companysemantics.ai</a></span></p>
808
+ </body>
809
+ </html>",
770
810
  "subject": "You've been invited to become a workspace owner",
771
811
  "text": "You have been invited to become the owner of "Acme Corp".
772
812
 
@@ -795,6 +835,16 @@ Questions? Contact support@companysemantics.ai
795
835
 
796
836
  exports[`email render > org.ownership_transfer_completed · Default 1`] = `
797
837
  {
838
+ "html": "<!DOCTYPE html>
839
+ <html lang="en">
840
+ <head><meta charset="UTF-8"></head>
841
+ <body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; margin: 0; padding: 0;">
842
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">The ownership of <b>&quot;Acme Corp&quot;</b> has been transferred to <b>new.owner@acme.com</b>.</p>
843
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">You are no longer the owner of this workspace, but you remain an admin.</p>
844
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">If you did not authorize this transfer, please contact support immediately.</p>
845
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0;">Company Semantics<br><span style="color: #888;">Questions? Contact <a href="mailto:support@companysemantics.ai" style="color: #888;">support@companysemantics.ai</a></span></p>
846
+ </body>
847
+ </html>",
798
848
  "subject": "Workspace ownership has been transferred",
799
849
  "text": "The ownership of "Acme Corp" has been transferred to new.owner@acme.com.
800
850
 
@@ -903,6 +953,19 @@ Questions? Contact support@companysemantics.ai
903
953
 
904
954
  exports[`email render > security.alert · Excessive OTP requests 1`] = `
905
955
  {
956
+ "html": "<!DOCTYPE html>
957
+ <html lang="en">
958
+ <head><meta charset="UTF-8"></head>
959
+ <body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; margin: 0; padding: 0;">
960
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">We detected an unusual number of login code requests for your account.</p>
961
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">Details: <b>12 login codes requested in 5 minutes</b></p>
962
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 4px 0;">If this was you, no action is needed.</p>
963
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 4px 0;">If you didn&#39;t request these codes, someone may be trying to access your account.</p>
964
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">We recommend reviewing your account security.</p>
965
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">Time: <b>Sun, 12 Jul 2026 14:30:00 GMT</b></p>
966
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0;">Company Semantics Security<br><span style="color: #888;">Questions? Contact <a href="mailto:support@companysemantics.ai" style="color: #888;">support@companysemantics.ai</a></span></p>
967
+ </body>
968
+ </html>",
906
969
  "subject": "Security alert for your account",
907
970
  "text": "We detected an unusual number of login code requests for your account.
908
971
 
@@ -923,6 +986,18 @@ Questions? Contact support@companysemantics.ai
923
986
 
924
987
  exports[`email render > security.alert · Unusual login location 1`] = `
925
988
  {
989
+ "html": "<!DOCTYPE html>
990
+ <html lang="en">
991
+ <head><meta charset="UTF-8"></head>
992
+ <body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; margin: 0; padding: 0;">
993
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">We detected a login attempt from an unusual location.</p>
994
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">Details: <b>Login from Berlin, DE</b></p>
995
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 4px 0;">If this was you, no action is needed.</p>
996
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">If you didn&#39;t attempt to log in, please secure your account immediately.</p>
997
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0 0 20px 0;">Time: <b>Sun, 12 Jul 2026 14:30:00 GMT</b></p>
998
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; margin: 0;">Company Semantics Security<br><span style="color: #888;">Questions? Contact <a href="mailto:support@companysemantics.ai" style="color: #888;">support@companysemantics.ai</a></span></p>
999
+ </body>
1000
+ </html>",
926
1001
  "subject": "Security alert for your account",
927
1002
  "text": "We detected a login attempt from an unusual location.
928
1003
 
@@ -33,13 +33,7 @@ export function renderAuthOtp(
33
33
 
34
34
  const blocks: Block[] = [
35
35
  paragraph("Copy/paste code in login form:"),
36
- ctaBox({
37
- label: otp,
38
- padding: "20px 24px",
39
- fontSize: "20px",
40
- borderRadiusZero: true,
41
- letterSpacing: "4px",
42
- }),
36
+ ctaBox({ label: otp, variant: "code" }),
43
37
  keyValue("Status", "VALID"),
44
38
  keyValue(
45
39
  "Expires in",
@@ -190,17 +190,35 @@ export interface CtaBoxOptions {
190
190
  /** When present, the label links to this URL (and the URL rides under the
191
191
  * plain-text box). */
192
192
  href?: string;
193
- /** Inner-cell padding, e.g. `"16px 24px"`. */
193
+ /** Visual kind. `"action"` = link CTA (default); `"code"` = copy/paste code
194
+ * box (square corners, letter-spaced). */
195
+ variant?: "action" | "code";
196
+ }
197
+
198
+ /** Component-owned styling per CTA variant. Templates supply only `label`/`href`/
199
+ * `variant` — the only allowable UI is the component, so all CSS lives here,
200
+ * never at the call site. */
201
+ interface CtaVariantStyle {
194
202
  padding: string;
195
- /** Label font-size, e.g. `"16px"`. */
196
203
  fontSize: string;
197
- /** Optional table `max-width`, e.g. `"220px"`. */
198
204
  maxWidth?: string;
199
- /** Emit `border-radius: 0;` (OTP code box). */
200
- borderRadiusZero?: boolean;
201
- /** Optional `letter-spacing`, e.g. `"4px"` (OTP code box). */
205
+ square: boolean;
202
206
  letterSpacing?: string;
203
207
  }
208
+ const CTA_VARIANTS: Record<"action" | "code", CtaVariantStyle> = {
209
+ action: {
210
+ padding: "16px 24px",
211
+ fontSize: "16px",
212
+ maxWidth: "220px",
213
+ square: false,
214
+ },
215
+ code: {
216
+ padding: "20px 24px",
217
+ fontSize: "16px",
218
+ square: true,
219
+ letterSpacing: "4px",
220
+ },
221
+ };
204
222
 
205
223
  /** Padding columns on each side of the `>> LABEL <<` line in the ASCII box. */
206
224
  const CTA_BOX_PAD = 3;
@@ -216,19 +234,13 @@ function asciiCtaBox(label: string): string {
216
234
  /** The bordered `>> LABEL <<` CTA box (OTP / JOIN / OPEN / VIEW TEAM / …). When
217
235
  * `href` is given, HTML links the label and plain text prints the URL below. */
218
236
  export function ctaBox(opts: CtaBoxOptions): Block {
219
- const {
220
- label,
221
- href,
222
- padding,
223
- fontSize,
224
- maxWidth,
225
- borderRadiusZero,
226
- letterSpacing,
227
- } = opts;
237
+ const { label, href, variant = "action" } = opts;
238
+ const { padding, fontSize, maxWidth, square, letterSpacing } =
239
+ CTA_VARIANTS[variant];
228
240
 
229
241
  const tableStyle =
230
242
  `border: 2px solid #1a1a1a;` +
231
- (borderRadiusZero ? ` border-radius: 0;` : ``) +
243
+ (square ? ` border-radius: 0;` : ``) +
232
244
  ` margin: 0 0 20px 0;` +
233
245
  (maxWidth ? ` max-width: ${maxWidth};` : ``);
234
246
 
@@ -31,13 +31,7 @@ export function renderChatShared(payload: ChatSharedPayload): Block[] {
31
31
 
32
32
  blocks.push(
33
33
  chatDots(),
34
- ctaBox({
35
- label: "SEE MORE",
36
- href: shareUrl,
37
- padding: "12px 20px",
38
- fontSize: "14px",
39
- maxWidth: "200px",
40
- }),
34
+ ctaBox({ label: "SEE MORE", href: shareUrl }),
41
35
  footer(`This share was sent via ${COMPANY_NAME}.`),
42
36
  paragraph(NOTICE, "tight"),
43
37
  signature(),
@@ -30,13 +30,7 @@ export function renderAccessApproved(payload: AccessApprovedPayload): Block[] {
30
30
  bold(`"${docTitle}"`),
31
31
  ` — you ${ACCESS_PHRASE[accessLevel]}.`,
32
32
  ]),
33
- ctaBox({
34
- label: "OPEN",
35
- href: docUrl,
36
- padding: "16px 24px",
37
- fontSize: "16px",
38
- maxWidth: "220px",
39
- }),
33
+ ctaBox({ label: "OPEN", href: docUrl }),
40
34
  footer(`This notification was sent via ${COMPANY_NAME}.`),
41
35
  signature(),
42
36
  ];
@@ -39,13 +39,7 @@ export function renderAccessRequested(
39
39
  if (message) blocks.push(chatUser(message, `Message from ${requesterName}`));
40
40
 
41
41
  blocks.push(
42
- ctaBox({
43
- label: "REVIEW",
44
- href: reviewUrl,
45
- padding: "16px 24px",
46
- fontSize: "16px",
47
- maxWidth: "220px",
48
- }),
42
+ ctaBox({ label: "REVIEW", href: reviewUrl }),
49
43
  footer(`This notification was sent via ${COMPANY_NAME}.`, OWNER_NOTE),
50
44
  signature(),
51
45
  );
@@ -22,12 +22,7 @@ export function renderOrgInvite(payload: OrgInvitePayload): Block[] {
22
22
 
23
23
  return [
24
24
  paragraph("Workspace invitation."),
25
- ctaBox({
26
- label: "JOIN",
27
- href: acceptUrl,
28
- padding: "20px 24px",
29
- fontSize: "16px",
30
- }),
25
+ ctaBox({ label: "JOIN", href: acceptUrl }),
31
26
  keyValue("From", inviterName),
32
27
  keyValue("Workspace", orgName),
33
28
  keyValue("Role", role),
@@ -33,13 +33,7 @@ export function renderOwnershipTransfer(
33
33
  if (note) blocks.push(chatUser(note, "Message from the current owner"));
34
34
 
35
35
  blocks.push(
36
- ctaBox({
37
- label: "ACCEPT",
38
- href: acceptUrl,
39
- padding: "16px 24px",
40
- fontSize: "16px",
41
- maxWidth: "220px",
42
- }),
36
+ ctaBox({ label: "ACCEPT", href: acceptUrl }),
43
37
  keyValue("Expires in", `${expiresInDays} days`, "normal"),
44
38
  footer(
45
39
  "If you did not expect this invitation, you can safely ignore this email.",
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Central email dispatcher. Given a kind + payload it returns
3
- * `{ subject, text, html? }`, pulling the subject from the registry (single
3
+ * `{ subject, text, html }`, pulling the subject from the registry (single
4
4
  * source of truth) and deriving both surfaces from ONE composed block list
5
5
  * (`htmlShell` + `textShell`). This is the one entry point both the backend
6
6
  * (real sends) and the app (Ladle preview) call.
@@ -28,8 +28,8 @@ export interface RenderedEmail {
28
28
  subject: string;
29
29
  /** Plain-text body. */
30
30
  text: string;
31
- /** HTML body (present only for html-supported kinds). */
32
- html?: string;
31
+ /** HTML body (every kind is dual-output). */
32
+ html: string;
33
33
  }
34
34
 
35
35
  /** Caller-supplied render options. */
@@ -55,30 +55,13 @@ export const IMPLEMENTED_EMAIL_KINDS = [
55
55
 
56
56
  export type ImplementedEmailKind = (typeof IMPLEMENTED_EMAIL_KINDS)[number];
57
57
 
58
- /**
59
- * Kinds delivered as plain text only. The unified block model renders HTML for
60
- * every template, but these keep their historical text-only surface — the
61
- * dispatcher omits `html` for them. (Enabling HTML later is deleting the entry.)
62
- */
63
- const TEXT_ONLY_KINDS = new Set<EmailKind>([
64
- "security.alert",
65
- "org.ownership_transfer",
66
- "org.ownership_transfer_completed",
67
- ]);
68
-
69
58
  /** Payload type for a kind (or `never` for kinds without a payload). */
70
59
  type PayloadFor<K extends EmailKind> = K extends keyof EmailPayloads
71
60
  ? EmailPayloads[K]
72
61
  : never;
73
62
 
74
- function toEmail(
75
- subject: string,
76
- kind: EmailKind,
77
- blocks: Block[],
78
- ): RenderedEmail {
79
- const email: RenderedEmail = { subject, text: textShell(blocks) };
80
- if (!TEXT_ONLY_KINDS.has(kind)) email.html = htmlShell(blocks);
81
- return email;
63
+ function toEmail(subject: string, blocks: Block[]): RenderedEmail {
64
+ return { subject, text: textShell(blocks), html: htmlShell(blocks) };
82
65
  }
83
66
 
84
67
  /**
@@ -100,19 +83,16 @@ export function renderEmail<K extends EmailKind>(
100
83
  case "auth.otp":
101
84
  return toEmail(
102
85
  subject,
103
- kind,
104
86
  renderAuthOtp(payload as EmailPayloads["auth.otp"], options),
105
87
  );
106
88
  case "org.invite":
107
89
  return toEmail(
108
90
  subject,
109
- kind,
110
91
  renderOrgInvite(payload as EmailPayloads["org.invite"]),
111
92
  );
112
93
  case "org.unit_owner_granted":
113
94
  return toEmail(
114
95
  subject,
115
- kind,
116
96
  renderUnitOwnerGranted(
117
97
  payload as EmailPayloads["org.unit_owner_granted"],
118
98
  ),
@@ -120,7 +100,6 @@ export function renderEmail<K extends EmailKind>(
120
100
  case "org.ownership_transfer":
121
101
  return toEmail(
122
102
  subject,
123
- kind,
124
103
  renderOwnershipTransfer(
125
104
  payload as EmailPayloads["org.ownership_transfer"],
126
105
  ),
@@ -128,7 +107,6 @@ export function renderEmail<K extends EmailKind>(
128
107
  case "org.ownership_transfer_completed":
129
108
  return toEmail(
130
109
  subject,
131
- kind,
132
110
  renderOwnershipTransferCompleted(
133
111
  payload as EmailPayloads["org.ownership_transfer_completed"],
134
112
  ),
@@ -136,25 +114,21 @@ export function renderEmail<K extends EmailKind>(
136
114
  case "security.alert":
137
115
  return toEmail(
138
116
  subject,
139
- kind,
140
117
  renderSecurityAlert(payload as EmailPayloads["security.alert"]),
141
118
  );
142
119
  case "chat.shared":
143
120
  return toEmail(
144
121
  subject,
145
- kind,
146
122
  renderChatShared(payload as EmailPayloads["chat.shared"]),
147
123
  );
148
124
  case "share.granted":
149
125
  return toEmail(
150
126
  subject,
151
- kind,
152
127
  renderShareGranted(payload as EmailPayloads["share.granted"]),
153
128
  );
154
129
  case "companyMd.access_requested":
155
130
  return toEmail(
156
131
  subject,
157
- kind,
158
132
  renderAccessRequested(
159
133
  payload as EmailPayloads["companyMd.access_requested"],
160
134
  ),
@@ -162,7 +136,6 @@ export function renderEmail<K extends EmailKind>(
162
136
  case "companyMd.access_request_approved":
163
137
  return toEmail(
164
138
  subject,
165
- kind,
166
139
  renderAccessApproved(
167
140
  payload as EmailPayloads["companyMd.access_request_approved"],
168
141
  ),
@@ -170,7 +143,6 @@ export function renderEmail<K extends EmailKind>(
170
143
  case "companyMd.access_request_denied":
171
144
  return toEmail(
172
145
  subject,
173
- kind,
174
146
  renderAccessDenied(
175
147
  payload as EmailPayloads["companyMd.access_request_denied"],
176
148
  ),
@@ -49,13 +49,7 @@ export function renderShareGranted(payload: ShareGrantedPayload): Block[] {
49
49
  if (message) blocks.push(chatUser(message, `Message from ${granterName}`));
50
50
 
51
51
  blocks.push(
52
- ctaBox({
53
- label: "OPEN",
54
- href: ctaUrl,
55
- padding: "16px 24px",
56
- fontSize: "16px",
57
- maxWidth: "220px",
58
- }),
52
+ ctaBox({ label: "OPEN", href: ctaUrl }),
59
53
  footer(`This notification was sent via ${COMPANY_NAME}.`, NOTICE),
60
54
  signature(),
61
55
  );
@@ -40,13 +40,7 @@ export function renderUnitOwnerGranted(
40
40
  if (message) blocks.push(chatUser(message, `Message from ${granterName}`));
41
41
 
42
42
  blocks.push(
43
- ctaBox({
44
- label: "VIEW TEAM",
45
- href: ctaUrl,
46
- padding: "16px 24px",
47
- fontSize: "16px",
48
- maxWidth: "220px",
49
- }),
43
+ ctaBox({ label: "VIEW TEAM", href: ctaUrl }),
50
44
  footer(`This notification was sent via ${COMPANY_NAME}.`, NOTICE),
51
45
  signature(),
52
46
  );
@@ -12,6 +12,10 @@
12
12
  * - `ownership` — entity.owner_user_id column projection
13
13
  * - `visibility` — entity.visibility tier compiled to viewer rows
14
14
  * - `inheritance` — org-chart-membership-based unit walk
15
+ * - `authority` — org-admin / account-owner / unit-editor / sharing-policy
16
+ * bands as synthetic live-resolved principals (ADR-BE-185)
17
+ * - `context_association` — a company.md context-bank doc inherits viewer access
18
+ * from every associated parent (the governed union, ADR-BE-392)
15
19
  * - `migration` — one-shot backfill from legacy systems (AUTH-004)
16
20
  */
17
21
  import { z } from "zod";
@@ -21,6 +25,8 @@ export const ACCESS_SOURCES = [
21
25
  "ownership",
22
26
  "visibility",
23
27
  "inheritance",
28
+ "authority",
29
+ "context_association",
24
30
  "migration",
25
31
  ] as const;
26
32
  export type AccessSource = (typeof ACCESS_SOURCES)[number];