@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.
@@ -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, SUPPORT_EMAIL } from "./constants";
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), 14px, configurable spacing.
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: 14px; margin: ${SPACING[spacing]};">${html}</p>`,
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: 14px; margin: ${SPACING[spacing]};">${html}</p>`,
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. Owns the full company sign-off in both surfaces: the
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: 14px; margin: ${SPACING.none};">${escapeHtml(signer)}<br><span style="color: #888;">Questions? Contact <a href="mailto:${SUPPORT_EMAIL}" style="color: #888;">${SUPPORT_EMAIL}</a></span></p>`,
157
- text: `---\n${signer}\nQuestions? Contact ${SUPPORT_EMAIL}`,
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: 20px; margin: 0 0 16px 0; max-width: 320px; text-align: center;">⋮</p>`,
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: "16px",
214
+ fontSize: FONT_SIZE,
212
215
  maxWidth: "220px",
213
216
  square: false,
214
217
  },
215
218
  code: {
216
- padding: "20px 24px",
217
- fontSize: "16px",
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. The avatar rides
323
- * the bottom (bottom-aligned like a real chat). When `from` is given it renders
324
- * as an attribution below, right-aligned to the message's right edge (clear of
325
- * the face). Doubles as the "message from …" quote.
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: 11px; color: #888; text-align: right; padding-top: 4px;">${escapeHtml(from)}</td>
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: 13px; background: #f5f5f5;">${escapeHtml(clamped)}</td>
340
- <td style="${MONO} font-size: 14px; padding-left: 8px; vertical-align: bottom;">(•̀_ರ╮)</td>
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
- const box = [`┌${border}┐`, ...body, `└${border}┘ (•̀_ರ╮)`];
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 (bottom-aligned). */
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: 14px; padding-right: 8px; vertical-align: bottom;">[c_S]</td>
360
- <td style="border: 1px solid #1a1a1a; border-radius: 12px 12px 12px 0; padding: 10px 14px; min-width: 280px; ${MONO} font-size: 13px; background: #f5f5f5;">${escapeHtml(clamped)}</td>
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 = "[c_S] └──────────────────────────────────────┘";
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, `Message from ${requesterName}`));
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, "Message from the current owner"));
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, `Message from ${granterName}`));
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, `Message from ${granterName}`));
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";
@@ -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/goals/docs/:slug/sharing.
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: EffectiveAccess;
46
+ readonly effectiveAccess: EffectiveAccessResponse;
101
47
  }
102
48
 
103
49
  /**