@company-semantics/contracts 28.1.0 → 30.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 +1 -1
- package/src/email/render/__tests__/__snapshots__/render-snapshot.test.ts.snap +206 -231
- package/src/email/render/auth-otp.ts +1 -7
- package/src/email/render/blocks.ts +58 -37
- package/src/email/render/chat-shared.ts +1 -7
- package/src/email/render/company-md-access-approved.ts +1 -7
- package/src/email/render/company-md-access-requested.ts +2 -8
- package/src/email/render/org-invite.ts +1 -6
- package/src/email/render/ownership-transfer.ts +2 -8
- package/src/email/render/share-granted.ts +2 -8
- package/src/email/render/unit-owner-granted.ts +2 -8
- package/src/index.ts +4 -6
- package/src/org/README.md +0 -5
- package/src/org/index.ts +5 -6
- package/src/org/sharing.ts +15 -69
|
@@ -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",
|
|
@@ -13,11 +13,15 @@
|
|
|
13
13
|
* for inline emphasis). No template hand-writes markup or raw strings.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { COMPANY_NAME, MONO_FONT_STACK
|
|
16
|
+
import { COMPANY_NAME, MONO_FONT_STACK } from "./constants";
|
|
17
17
|
import { escapeHtml } from "./escape-html";
|
|
18
18
|
|
|
19
19
|
const MONO = `font-family: ${MONO_FONT_STACK};`;
|
|
20
20
|
|
|
21
|
+
/** The single font size for every email element (HTML). Plain text is monospace
|
|
22
|
+
* so it carries no size — this keeps one visual size across both surfaces. */
|
|
23
|
+
const FONT_SIZE = "13px";
|
|
24
|
+
|
|
21
25
|
// =============================================================================
|
|
22
26
|
// Core types
|
|
23
27
|
// =============================================================================
|
|
@@ -100,7 +104,7 @@ export function textShell(blocks: Block[]): string {
|
|
|
100
104
|
// =============================================================================
|
|
101
105
|
|
|
102
106
|
/**
|
|
103
|
-
* Email paragraph — mono (matching the body shell),
|
|
107
|
+
* Email paragraph — mono (matching the body shell), 13px, configurable spacing.
|
|
104
108
|
* The single paragraph primitive: greeting / keyValue / footer / signature all
|
|
105
109
|
* build on it, and templates use it directly for body lines.
|
|
106
110
|
*/
|
|
@@ -110,7 +114,7 @@ export function paragraph(
|
|
|
110
114
|
): Block {
|
|
111
115
|
const { html, text } = renderInline(content);
|
|
112
116
|
return {
|
|
113
|
-
html: `<p style="${MONO} font-size:
|
|
117
|
+
html: `<p style="${MONO} font-size: ${FONT_SIZE}; margin: ${SPACING[spacing]};">${html}</p>`,
|
|
114
118
|
text,
|
|
115
119
|
spacing,
|
|
116
120
|
};
|
|
@@ -141,20 +145,19 @@ export function footer(
|
|
|
141
145
|
: escapeHtml(firstLine);
|
|
142
146
|
const text = secondLine ? `${firstLine}\n${secondLine}` : firstLine;
|
|
143
147
|
return {
|
|
144
|
-
html: `<p style="${MONO} font-size:
|
|
148
|
+
html: `<p style="${MONO} font-size: ${FONT_SIZE}; margin: ${SPACING[spacing]};">${html}</p>`,
|
|
145
149
|
text,
|
|
146
150
|
spacing,
|
|
147
151
|
};
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
/**
|
|
151
|
-
* Trailing sign-off
|
|
152
|
-
* company (or custom `signer`) name and a "Questions? Contact <support>" line.
|
|
155
|
+
* Trailing sign-off — the company (or custom `signer`) name.
|
|
153
156
|
*/
|
|
154
157
|
export function signature(signer: string = COMPANY_NAME): Block {
|
|
155
158
|
return {
|
|
156
|
-
html: `<p style="${MONO} font-size:
|
|
157
|
-
text: `---\n${signer}
|
|
159
|
+
html: `<p style="${MONO} font-size: ${FONT_SIZE}; margin: ${SPACING.none};">${escapeHtml(signer)}</p>`,
|
|
160
|
+
text: `---\n${signer}`,
|
|
158
161
|
spacing: "none",
|
|
159
162
|
};
|
|
160
163
|
}
|
|
@@ -173,7 +176,7 @@ export const ACCESS_PHRASE: Record<"editor" | "commenter" | "viewer", string> =
|
|
|
173
176
|
/** Centered continuation dots separator (chat-shared). */
|
|
174
177
|
export function chatDots(): Block {
|
|
175
178
|
return {
|
|
176
|
-
html: `<p style="${MONO} font-size:
|
|
179
|
+
html: `<p style="${MONO} font-size: ${FONT_SIZE}; margin: 0 0 16px 0; max-width: 320px; text-align: center;">⋮</p>`,
|
|
177
180
|
text: " ⋮",
|
|
178
181
|
spacing: "normal",
|
|
179
182
|
};
|
|
@@ -190,17 +193,36 @@ export interface CtaBoxOptions {
|
|
|
190
193
|
/** When present, the label links to this URL (and the URL rides under the
|
|
191
194
|
* plain-text box). */
|
|
192
195
|
href?: string;
|
|
193
|
-
/**
|
|
196
|
+
/** Visual kind. `"action"` = link CTA (default); `"code"` = copy/paste code
|
|
197
|
+
* box (square corners, letter-spaced). */
|
|
198
|
+
variant?: "action" | "code";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Component-owned styling per CTA variant. Templates supply only `label`/`href`/
|
|
202
|
+
* `variant` — the only allowable UI is the component, so all CSS lives here,
|
|
203
|
+
* never at the call site. */
|
|
204
|
+
interface CtaVariantStyle {
|
|
194
205
|
padding: string;
|
|
195
|
-
/** Label font-size, e.g. `"16px"`. */
|
|
196
206
|
fontSize: string;
|
|
197
|
-
/** Optional table `max-width`, e.g. `"220px"`. */
|
|
198
207
|
maxWidth?: string;
|
|
199
|
-
|
|
200
|
-
borderRadiusZero?: boolean;
|
|
201
|
-
/** Optional `letter-spacing`, e.g. `"4px"` (OTP code box). */
|
|
208
|
+
square: boolean;
|
|
202
209
|
letterSpacing?: string;
|
|
203
210
|
}
|
|
211
|
+
const CTA_VARIANTS: Record<"action" | "code", CtaVariantStyle> = {
|
|
212
|
+
action: {
|
|
213
|
+
padding: "16px 24px",
|
|
214
|
+
fontSize: FONT_SIZE,
|
|
215
|
+
maxWidth: "220px",
|
|
216
|
+
square: false,
|
|
217
|
+
},
|
|
218
|
+
code: {
|
|
219
|
+
padding: "16px 24px",
|
|
220
|
+
fontSize: FONT_SIZE,
|
|
221
|
+
maxWidth: "220px",
|
|
222
|
+
square: true,
|
|
223
|
+
letterSpacing: "4px",
|
|
224
|
+
},
|
|
225
|
+
};
|
|
204
226
|
|
|
205
227
|
/** Padding columns on each side of the `>> LABEL <<` line in the ASCII box. */
|
|
206
228
|
const CTA_BOX_PAD = 3;
|
|
@@ -216,19 +238,13 @@ function asciiCtaBox(label: string): string {
|
|
|
216
238
|
/** The bordered `>> LABEL <<` CTA box (OTP / JOIN / OPEN / VIEW TEAM / …). When
|
|
217
239
|
* `href` is given, HTML links the label and plain text prints the URL below. */
|
|
218
240
|
export function ctaBox(opts: CtaBoxOptions): Block {
|
|
219
|
-
const {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
padding,
|
|
223
|
-
fontSize,
|
|
224
|
-
maxWidth,
|
|
225
|
-
borderRadiusZero,
|
|
226
|
-
letterSpacing,
|
|
227
|
-
} = opts;
|
|
241
|
+
const { label, href, variant = "action" } = opts;
|
|
242
|
+
const { padding, fontSize, maxWidth, square, letterSpacing } =
|
|
243
|
+
CTA_VARIANTS[variant];
|
|
228
244
|
|
|
229
245
|
const tableStyle =
|
|
230
246
|
`border: 2px solid #1a1a1a;` +
|
|
231
|
-
(
|
|
247
|
+
(square ? ` border-radius: 0;` : ``) +
|
|
232
248
|
` margin: 0 0 20px 0;` +
|
|
233
249
|
(maxWidth ? ` max-width: ${maxWidth};` : ``);
|
|
234
250
|
|
|
@@ -307,10 +323,10 @@ function clampMessage(text: string): string {
|
|
|
307
323
|
}
|
|
308
324
|
|
|
309
325
|
/**
|
|
310
|
-
* Right-aligned user chat message. Truncated by clampMessage.
|
|
311
|
-
*
|
|
312
|
-
* as an attribution below, right-aligned to the message's right
|
|
313
|
-
*
|
|
326
|
+
* Right-aligned user chat message. Truncated by clampMessage. In plain text the
|
|
327
|
+
* face sits beside the last message line. When `from` is given (the sender's
|
|
328
|
+
* name) it renders as an attribution below, right-aligned to the message's right
|
|
329
|
+
* edge.
|
|
314
330
|
*/
|
|
315
331
|
export function chatUser(content: string, from?: string): Block {
|
|
316
332
|
const clamped = clampMessage(content);
|
|
@@ -318,42 +334,47 @@ export function chatUser(content: string, from?: string): Block {
|
|
|
318
334
|
const attributionRow = from
|
|
319
335
|
? `
|
|
320
336
|
<tr>
|
|
321
|
-
<td style="${MONO} font-size:
|
|
337
|
+
<td style="${MONO} font-size: ${FONT_SIZE}; color: #888; text-align: right; padding-top: 4px;">${escapeHtml(from)}</td>
|
|
322
338
|
<td></td>
|
|
323
339
|
</tr>`
|
|
324
340
|
: "";
|
|
325
341
|
const html = `<table cellpadding="0" cellspacing="0" border="0" style="margin: 0 0 16px 0;">
|
|
326
342
|
<tr>
|
|
327
|
-
<td style="border: 1px solid #1a1a1a; border-radius: 12px 12px 0 12px; padding: 10px 14px; min-width: 280px; text-align: right; ${MONO} font-size:
|
|
328
|
-
<td style="${MONO} font-size:
|
|
343
|
+
<td style="border: 1px solid #1a1a1a; border-radius: 12px 12px 0 12px; padding: 10px 14px; min-width: 280px; text-align: right; ${MONO} font-size: ${FONT_SIZE}; background: #f5f5f5;">${escapeHtml(clamped)}</td>
|
|
344
|
+
<td style="${MONO} font-size: ${FONT_SIZE}; padding-left: 8px; vertical-align: bottom;">(•̀_ರ╮)</td>
|
|
329
345
|
</tr>${attributionRow}
|
|
330
346
|
</table>`;
|
|
331
347
|
|
|
332
348
|
const border = "─".repeat(MESSAGE_WIDTH + 1);
|
|
333
349
|
const lines = wrapText(clamped, MESSAGE_WIDTH);
|
|
334
350
|
const body = lines.map((line) => `│${line.padStart(MESSAGE_WIDTH)} │`);
|
|
335
|
-
|
|
351
|
+
// Face beside the last message line (one row up from the bottom border).
|
|
352
|
+
body[body.length - 1] += " (•̀_ರ╮)";
|
|
353
|
+
const box = [`┌${border}┐`, ...body, `└${border}┘`];
|
|
336
354
|
if (from) box.push(from.padStart(MESSAGE_WIDTH + 3));
|
|
337
355
|
|
|
338
356
|
return { html, text: box.join("\n"), spacing: "normal" };
|
|
339
357
|
}
|
|
340
358
|
|
|
341
|
-
/** Left-aligned assistant chat message with `[c_S]` avatar
|
|
359
|
+
/** Left-aligned assistant chat message with `[c_S]` avatar; in plain text the
|
|
360
|
+
* avatar sits beside the last message line (one row up from the bottom border). */
|
|
342
361
|
export function chatAssistant(content: string): Block {
|
|
343
362
|
const clamped = clampMessage(content);
|
|
344
363
|
|
|
345
364
|
const html = `<table cellpadding="0" cellspacing="0" border="0" style="margin: 16px 0;">
|
|
346
365
|
<tr>
|
|
347
|
-
<td style="${MONO} font-size:
|
|
348
|
-
<td style="border: 1px solid #1a1a1a; border-radius: 12px 12px 12px 0; padding: 10px 14px; min-width: 280px; ${MONO} font-size:
|
|
366
|
+
<td style="${MONO} font-size: ${FONT_SIZE}; padding-right: 8px; vertical-align: bottom;">[c_S]</td>
|
|
367
|
+
<td style="border: 1px solid #1a1a1a; border-radius: 12px 12px 12px 0; padding: 10px 14px; min-width: 280px; ${MONO} font-size: ${FONT_SIZE}; background: #f5f5f5;">${escapeHtml(clamped)}</td>
|
|
349
368
|
</tr>
|
|
350
369
|
</table>`;
|
|
351
370
|
|
|
352
371
|
const lineWidth = 36;
|
|
353
372
|
const lines = wrapText(clamped, lineWidth);
|
|
354
373
|
const top = " ┌──────────────────────────────────────┐";
|
|
355
|
-
const bot = "
|
|
374
|
+
const bot = " └──────────────────────────────────────┘";
|
|
356
375
|
const boxLines = lines.map((line) => ` │ ${line.padEnd(lineWidth)} │`);
|
|
376
|
+
// Avatar beside the last message line (one row up from the bottom border).
|
|
377
|
+
boxLines[boxLines.length - 1] = `[c_S] ${boxLines[boxLines.length - 1].slice(7)}`;
|
|
357
378
|
|
|
358
379
|
return { html, text: [top, ...boxLines, bot].join("\n"), spacing: "normal" };
|
|
359
380
|
}
|
|
@@ -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
|
];
|
|
@@ -36,16 +36,10 @@ export function renderAccessRequested(
|
|
|
36
36
|
]),
|
|
37
37
|
];
|
|
38
38
|
|
|
39
|
-
if (message) blocks.push(chatUser(message,
|
|
39
|
+
if (message) blocks.push(chatUser(message, 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),
|
|
@@ -30,16 +30,10 @@ export function renderOwnershipTransfer(
|
|
|
30
30
|
]),
|
|
31
31
|
];
|
|
32
32
|
|
|
33
|
-
if (note) blocks.push(chatUser(note, "
|
|
33
|
+
if (note) blocks.push(chatUser(note, "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.",
|
|
@@ -46,16 +46,10 @@ export function renderShareGranted(payload: ShareGrantedPayload): Block[] {
|
|
|
46
46
|
]),
|
|
47
47
|
];
|
|
48
48
|
|
|
49
|
-
if (message) blocks.push(chatUser(message,
|
|
49
|
+
if (message) blocks.push(chatUser(message, 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
|
);
|
|
@@ -37,16 +37,10 @@ export function renderUnitOwnerGranted(
|
|
|
37
37
|
]),
|
|
38
38
|
];
|
|
39
39
|
|
|
40
|
-
if (message) blocks.push(chatUser(message,
|
|
40
|
+
if (message) blocks.push(chatUser(message, 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
|
);
|
package/src/index.ts
CHANGED
|
@@ -352,15 +352,13 @@ export type {
|
|
|
352
352
|
CompanyMdTreeNode,
|
|
353
353
|
CompanyMdDoc,
|
|
354
354
|
CompanyMdContextBankItem,
|
|
355
|
-
// Sharing and ACL types (PRD-00306)
|
|
355
|
+
// Sharing and ACL types (PRD-00306). Legacy AccessSource / AccessReason /
|
|
356
|
+
// EffectiveAccess / EvaluationStep / AccessExplanation removed with
|
|
357
|
+
// CompanyMdAccessEvaluator (ADR-BE-392); the ONE AccessSource is re-exported
|
|
358
|
+
// from ./permissions below.
|
|
356
359
|
AccessLevel,
|
|
357
360
|
SharePolicy,
|
|
358
361
|
AclEntry,
|
|
359
|
-
AccessSource,
|
|
360
|
-
AccessReason,
|
|
361
|
-
EffectiveAccess,
|
|
362
|
-
EvaluationStep,
|
|
363
|
-
AccessExplanation,
|
|
364
362
|
ShareState,
|
|
365
363
|
PermissionAuditEntry,
|
|
366
364
|
} from "./org/index";
|
package/src/org/README.md
CHANGED
|
@@ -17,10 +17,7 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
17
17
|
## Public API
|
|
18
18
|
|
|
19
19
|
- `AcceptInviteRequest` _(type)_ — Request payload for accepting an organization invite.
|
|
20
|
-
- `AccessExplanation` _(type)_ — Full access explanation with evaluation trace.
|
|
21
20
|
- `AccessLevel` _(type)_ — Document access level.
|
|
22
|
-
- `AccessReason` _(type)_
|
|
23
|
-
- `AccessSource` _(type)_ — Source of an access grant.
|
|
24
21
|
- `AcknowledgeSystemEventResponse` _(type)_
|
|
25
22
|
- `AcknowledgeSystemEventResponseSchema`
|
|
26
23
|
- `AclEntry` _(type)_
|
|
@@ -93,8 +90,6 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
93
90
|
- `DomainResponseSchema`
|
|
94
91
|
- `DomainStatus` _(type)_ — Status of a domain claim within an organization. - 'pending': Domain claimed but not yet verified -…
|
|
95
92
|
- `DomainVerificationMethod` _(type)_ — Method used to verify domain ownership. - 'dnstxt': DNS TXT record verification (primary method) - 'email'…
|
|
96
|
-
- `EffectiveAccess` _(type)_ — Effective access for a user on a document.
|
|
97
|
-
- `EvaluationStep` _(type)_ — Single step in the access evaluation trace.
|
|
98
93
|
- `ExecutionContext` _(type)_
|
|
99
94
|
- `ExecutionContextSchema` — ExecutionContext — the RUN in which an intent was produced or applied.
|
|
100
95
|
- `ExecutionScope` _(type)_ — Execution scope determines whose identity is used when executing actions. - 'self': Actions execute under the…
|
package/src/org/index.ts
CHANGED
|
@@ -227,16 +227,15 @@ export type {
|
|
|
227
227
|
CompanyMdContextBankItem,
|
|
228
228
|
} from "./company-md";
|
|
229
229
|
|
|
230
|
-
// Sharing and ACL types (PRD-00306)
|
|
230
|
+
// Sharing and ACL types (PRD-00306). The legacy AccessSource / AccessReason /
|
|
231
|
+
// EffectiveAccess / EvaluationStep / AccessExplanation were removed with
|
|
232
|
+
// CompanyMdAccessEvaluator (ADR-BE-392); ShareState.effectiveAccess now carries
|
|
233
|
+
// the projection EffectiveAccessResponse, and the ONE AccessSource lives in
|
|
234
|
+
// permissions/access-source.
|
|
231
235
|
export type {
|
|
232
236
|
AccessLevel,
|
|
233
237
|
SharePolicy,
|
|
234
238
|
AclEntry,
|
|
235
|
-
AccessSource,
|
|
236
|
-
AccessReason,
|
|
237
|
-
EffectiveAccess,
|
|
238
|
-
EvaluationStep,
|
|
239
|
-
AccessExplanation,
|
|
240
239
|
ShareState,
|
|
241
240
|
PermissionAuditEntry,
|
|
242
241
|
} from "./sharing";
|
package/src/org/sharing.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { EffectiveAccessResponse } from "../permissions/share-api.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Document access level.
|
|
3
5
|
* Privilege order: editor > commenter > viewer.
|
|
@@ -23,81 +25,25 @@ export interface AclEntry {
|
|
|
23
25
|
readonly grantedAt: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
/**
|
|
27
|
-
* Source of an access grant.
|
|
28
|
-
* Used in EffectiveAccess.reasons and EvaluationStep.source.
|
|
29
|
-
*
|
|
30
|
-
* `unit_delegation` covers leadership/delegation grants on the doc's owning
|
|
31
|
-
* unit via the authority projection (ADR-BE-169). Distinct from
|
|
32
|
-
* `unit_baseline` (which is membership-role-based) — delegations live on the
|
|
33
|
-
* `org_unit_authority_grants` table and carry an explicit scope set.
|
|
34
|
-
*
|
|
35
|
-
* `visibility` covers the general-access band (`private` | `unit` | `org` on
|
|
36
|
-
* the entity's `visibility` column) — the "General access" tier in the share
|
|
37
|
-
* dialog. `unit` grants viewer to everyone home-in or matrixed-into the owning
|
|
38
|
-
* unit's subtree (members ∪ contributors, flowing down); at the org root `unit`
|
|
39
|
-
* normalizes to org-wide. `org` grants viewer to every org member. This is a
|
|
40
|
-
* first-class source so the deterministic evaluator, the in-memory open gate,
|
|
41
|
-
* and the materialized grant-compiler all resolve the band identically. See
|
|
42
|
-
* ADR-CONTRACTS-075 and backend ADR-BE-374.
|
|
43
|
-
*/
|
|
44
|
-
export type AccessSource =
|
|
45
|
-
| "org_rbac"
|
|
46
|
-
| "sharing_policy"
|
|
47
|
-
| "visibility"
|
|
48
|
-
| "unit_baseline"
|
|
49
|
-
| "unit_delegation"
|
|
50
|
-
| "acl_grant"
|
|
51
|
-
| "doc_ownership";
|
|
52
|
-
|
|
53
|
-
export interface AccessReason {
|
|
54
|
-
readonly source: AccessSource;
|
|
55
|
-
readonly detail: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Effective access for a user on a document.
|
|
60
|
-
*
|
|
61
|
-
* INVARIANTS:
|
|
62
|
-
* - canShare is granted ONLY by: doc ownership, ownership of the owning unit,
|
|
63
|
-
* or org-level org.manage_goals / org.manage_company_md.
|
|
64
|
-
* - canShare is NEVER granted by ACL entries or sharing policy.
|
|
65
|
-
* - org_edit via sharing policy does NOT imply canShare.
|
|
66
|
-
* - Unit membership grants baseline access ONLY to resources owned by that unit.
|
|
67
|
-
*/
|
|
68
|
-
export interface EffectiveAccess {
|
|
69
|
-
readonly level: "none" | AccessLevel;
|
|
70
|
-
readonly reasons: ReadonlyArray<AccessReason>;
|
|
71
|
-
readonly canShare: boolean;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Single step in the access evaluation trace.
|
|
76
|
-
* Enables full traceability for debugging, audit UI, and compliance.
|
|
77
|
-
*/
|
|
78
|
-
export interface EvaluationStep {
|
|
79
|
-
readonly source: AccessSource;
|
|
80
|
-
readonly checked: boolean;
|
|
81
|
-
readonly granted: AccessLevel | null;
|
|
82
|
-
readonly detail: string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Full access explanation with evaluation trace.
|
|
87
|
-
* Returned by GoalsAccessEvaluator.explain().
|
|
88
|
-
*/
|
|
89
|
-
export interface AccessExplanation extends EffectiveAccess {
|
|
90
|
-
readonly evaluationTrace: ReadonlyArray<EvaluationStep>;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
28
|
/**
|
|
94
29
|
* Complete sharing state for a document.
|
|
95
|
-
* Returned by GET /api/
|
|
30
|
+
* Returned by GET /api/company-md/docs/:id/sharing.
|
|
31
|
+
*
|
|
32
|
+
* `effectiveAccess` is the ONE effective-access authority's answer
|
|
33
|
+
* ({@link EffectiveAccessResponse}: a nullable `access_level` + the
|
|
34
|
+
* `source_chain` of provenance tokens from the single `access-source` vocabulary
|
|
35
|
+
* — `explicit` | `ownership` | `visibility` | `inheritance` | `authority` |
|
|
36
|
+
* `context_association` | `migration`). The legacy per-doc `EffectiveAccess` /
|
|
37
|
+
* `AccessExplanation` shapes (their own 7-token `AccessSource`, `reasons`,
|
|
38
|
+
* `evaluationTrace`) were removed when `CompanyMdAccessEvaluator` was deleted and
|
|
39
|
+
* the response was collapsed onto the single authority (ADR-BE-392). There is now
|
|
40
|
+
* ONE `AccessSource` in the package (the projection one, from
|
|
41
|
+
* `permissions/access-source`).
|
|
96
42
|
*/
|
|
97
43
|
export interface ShareState {
|
|
98
44
|
readonly sharingPolicy: SharePolicy;
|
|
99
45
|
readonly acl: ReadonlyArray<AclEntry>;
|
|
100
|
-
readonly effectiveAccess:
|
|
46
|
+
readonly effectiveAccess: EffectiveAccessResponse;
|
|
101
47
|
}
|
|
102
48
|
|
|
103
49
|
/**
|