@company-semantics/contracts 28.0.0 → 28.1.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": "28.1.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -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
 
@@ -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
  ),
@@ -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];