@company-semantics/contracts 29.0.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 +199 -224
- package/src/email/render/blocks.ts +33 -24
- package/src/email/render/company-md-access-requested.ts +1 -1
- package/src/email/render/ownership-transfer.ts +1 -1
- package/src/email/render/share-granted.ts +1 -1
- package/src/email/render/unit-owner-granted.ts +1 -1
- 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
|
@@ -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
|
};
|
|
@@ -208,13 +211,14 @@ interface CtaVariantStyle {
|
|
|
208
211
|
const CTA_VARIANTS: Record<"action" | "code", CtaVariantStyle> = {
|
|
209
212
|
action: {
|
|
210
213
|
padding: "16px 24px",
|
|
211
|
-
fontSize:
|
|
214
|
+
fontSize: FONT_SIZE,
|
|
212
215
|
maxWidth: "220px",
|
|
213
216
|
square: false,
|
|
214
217
|
},
|
|
215
218
|
code: {
|
|
216
|
-
padding: "
|
|
217
|
-
fontSize:
|
|
219
|
+
padding: "16px 24px",
|
|
220
|
+
fontSize: FONT_SIZE,
|
|
221
|
+
maxWidth: "220px",
|
|
218
222
|
square: true,
|
|
219
223
|
letterSpacing: "4px",
|
|
220
224
|
},
|
|
@@ -319,10 +323,10 @@ function clampMessage(text: string): string {
|
|
|
319
323
|
}
|
|
320
324
|
|
|
321
325
|
/**
|
|
322
|
-
* Right-aligned user chat message. Truncated by clampMessage.
|
|
323
|
-
*
|
|
324
|
-
* as an attribution below, right-aligned to the message's right
|
|
325
|
-
*
|
|
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.
|
|
326
330
|
*/
|
|
327
331
|
export function chatUser(content: string, from?: string): Block {
|
|
328
332
|
const clamped = clampMessage(content);
|
|
@@ -330,42 +334,47 @@ export function chatUser(content: string, from?: string): Block {
|
|
|
330
334
|
const attributionRow = from
|
|
331
335
|
? `
|
|
332
336
|
<tr>
|
|
333
|
-
<td style="${MONO} font-size:
|
|
337
|
+
<td style="${MONO} font-size: ${FONT_SIZE}; color: #888; text-align: right; padding-top: 4px;">${escapeHtml(from)}</td>
|
|
334
338
|
<td></td>
|
|
335
339
|
</tr>`
|
|
336
340
|
: "";
|
|
337
341
|
const html = `<table cellpadding="0" cellspacing="0" border="0" style="margin: 0 0 16px 0;">
|
|
338
342
|
<tr>
|
|
339
|
-
<td style="border: 1px solid #1a1a1a; border-radius: 12px 12px 0 12px; padding: 10px 14px; min-width: 280px; text-align: right; ${MONO} font-size:
|
|
340
|
-
<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>
|
|
341
345
|
</tr>${attributionRow}
|
|
342
346
|
</table>`;
|
|
343
347
|
|
|
344
348
|
const border = "─".repeat(MESSAGE_WIDTH + 1);
|
|
345
349
|
const lines = wrapText(clamped, MESSAGE_WIDTH);
|
|
346
350
|
const body = lines.map((line) => `│${line.padStart(MESSAGE_WIDTH)} │`);
|
|
347
|
-
|
|
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}┘`];
|
|
348
354
|
if (from) box.push(from.padStart(MESSAGE_WIDTH + 3));
|
|
349
355
|
|
|
350
356
|
return { html, text: box.join("\n"), spacing: "normal" };
|
|
351
357
|
}
|
|
352
358
|
|
|
353
|
-
/** 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). */
|
|
354
361
|
export function chatAssistant(content: string): Block {
|
|
355
362
|
const clamped = clampMessage(content);
|
|
356
363
|
|
|
357
364
|
const html = `<table cellpadding="0" cellspacing="0" border="0" style="margin: 16px 0;">
|
|
358
365
|
<tr>
|
|
359
|
-
<td style="${MONO} font-size:
|
|
360
|
-
<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>
|
|
361
368
|
</tr>
|
|
362
369
|
</table>`;
|
|
363
370
|
|
|
364
371
|
const lineWidth = 36;
|
|
365
372
|
const lines = wrapText(clamped, lineWidth);
|
|
366
373
|
const top = " ┌──────────────────────────────────────┐";
|
|
367
|
-
const bot = "
|
|
374
|
+
const bot = " └──────────────────────────────────────┘";
|
|
368
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)}`;
|
|
369
378
|
|
|
370
379
|
return { html, text: [top, ...boxLines, bot].join("\n"), spacing: "normal" };
|
|
371
380
|
}
|
|
@@ -36,7 +36,7 @@ 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
42
|
ctaBox({ label: "REVIEW", href: reviewUrl }),
|
|
@@ -30,7 +30,7 @@ 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
36
|
ctaBox({ label: "ACCEPT", href: acceptUrl }),
|
|
@@ -46,7 +46,7 @@ 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
52
|
ctaBox({ label: "OPEN", href: ctaUrl }),
|
|
@@ -37,7 +37,7 @@ 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
43
|
ctaBox({ label: "VIEW TEAM", href: ctaUrl }),
|
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
|
/**
|