@company-semantics/contracts 40.0.0 → 42.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 +5 -5
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +8 -0
- package/src/chat/README.md +1 -1
- package/src/chat/schemas.ts +18 -2
- package/src/content/schemas.ts +10 -0
- package/src/notifications/__tests__/__snapshots__/registry.test.ts.snap +1 -0
- package/src/notifications/__tests__/__snapshots__/render-snapshot.test.ts.snap +179 -0
- package/src/notifications/__tests__/fixtures.ts +9 -0
- package/src/notifications/__tests__/render-snapshot.test.ts +9 -0
- package/src/notifications/kinds/company-md-access-approved.ts +20 -6
- package/src/notifications/payloads.ts +6 -0
- package/src/org/company-md.ts +9 -0
- package/src/permissions/access-request.ts +73 -2
- package/src/security/token-policy.ts +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@company-semantics/contracts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "42.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -129,17 +129,17 @@
|
|
|
129
129
|
"zod": "^4.4.3"
|
|
130
130
|
},
|
|
131
131
|
"devDependencies": {
|
|
132
|
-
"@types/node": "^
|
|
132
|
+
"@types/node": "^26.1.2",
|
|
133
133
|
"amphtml-validator": "^1.0.38",
|
|
134
134
|
"culori": "^4.0.2",
|
|
135
135
|
"husky": "^9.1.7",
|
|
136
|
-
"lint-staged": "^17.
|
|
137
|
-
"markdownlint-cli2": "^0.23.
|
|
136
|
+
"lint-staged": "^17.2.0",
|
|
137
|
+
"markdownlint-cli2": "^0.23.2",
|
|
138
138
|
"openapi-typescript": "^7.13.0",
|
|
139
139
|
"prettier": "^3.9.6",
|
|
140
140
|
"tsx": "^4.23.1",
|
|
141
141
|
"typescript": "^5.8.3",
|
|
142
|
-
"vite": "^
|
|
142
|
+
"vite": "^8.2.0",
|
|
143
143
|
"vitest": "^4.1.10",
|
|
144
144
|
"yaml": "^2.9.0"
|
|
145
145
|
},
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
|
|
2
|
-
export const SPEC_HASH = '
|
|
3
|
-
export const SPEC_HASH_FULL = '
|
|
2
|
+
export const SPEC_HASH = 'c0f32d11bf0f' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = 'c0f32d11bf0fdc59ee0a7bf84bee984f2f8109838d2659b6e66709e0198dcd4e' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -4603,6 +4603,11 @@ export interface components {
|
|
|
4603
4603
|
/** @enum {string} */
|
|
4604
4604
|
accessState?: "full" | "redacted_admin" | "locked_requestable";
|
|
4605
4605
|
canRequestAccess?: boolean;
|
|
4606
|
+
pendingAccessRequest?: {
|
|
4607
|
+
id: string;
|
|
4608
|
+
requestedAccessLevel: ("editor" | "commenter" | "viewer") | null;
|
|
4609
|
+
createdAt: string;
|
|
4610
|
+
} | null;
|
|
4606
4611
|
/** @enum {string} */
|
|
4607
4612
|
visibility: "private" | "unit" | "org";
|
|
4608
4613
|
/** @enum {string} */
|
|
@@ -4726,9 +4731,12 @@ export interface components {
|
|
|
4726
4731
|
ApproveAccessRequest: {
|
|
4727
4732
|
/** @enum {string} */
|
|
4728
4733
|
accessLevel: "editor" | "commenter" | "viewer";
|
|
4734
|
+
notify?: boolean;
|
|
4735
|
+
message?: string;
|
|
4729
4736
|
};
|
|
4730
4737
|
DenyAccessRequest: {
|
|
4731
4738
|
reason?: string;
|
|
4739
|
+
notify?: boolean;
|
|
4732
4740
|
};
|
|
4733
4741
|
/** @description Org-wide context-doc discovery hits the caller may view. */
|
|
4734
4742
|
SearchContextDocsResponse: {
|
package/src/chat/README.md
CHANGED
|
@@ -42,7 +42,7 @@ Shared types for chat persistence, sharing, real-time events, and runtime profil
|
|
|
42
42
|
- `ChatRuntimeProfile` _(type)_ — Chat runtime profile — user-selectable orchestration strategy.
|
|
43
43
|
- `ChatRuntimeProfileInfo` _(type)_ — Runtime profile metadata for UI rendering.
|
|
44
44
|
- `ChatShareInfo` _(type)_ — Chat share record with snapshot metadata.
|
|
45
|
-
- `ChatShareInfoSchema`
|
|
45
|
+
- `ChatShareInfoSchema` — A chat share, as returned by the share APIs.
|
|
46
46
|
- `ChatSseEvent` _(type)_ — SSE event types for chat updates.
|
|
47
47
|
- `ChatSseEventSchema`
|
|
48
48
|
- `ChatSuccessResponse` _(type)_ — Generic mutation success confirmation.
|
package/src/chat/schemas.ts
CHANGED
|
@@ -58,12 +58,28 @@ export const SharedChatMessageSchema = z.object({
|
|
|
58
58
|
parts: z.array(z.unknown()).optional(),
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* A chat share, as returned by the share APIs.
|
|
63
|
+
*
|
|
64
|
+
* SHOW-ONCE (ADR-BE-460). `token` and `shareUrl` are present ONLY on the
|
|
65
|
+
* response that creates the share. Share tokens are stored as a digest, so the
|
|
66
|
+
* server cannot redisplay one afterwards — listing or updating a share returns
|
|
67
|
+
* the record without them, and re-obtaining a link means revoke-and-reshare.
|
|
68
|
+
*
|
|
69
|
+
* This is the industry norm for capability links, and it is what removes the
|
|
70
|
+
* last reason to keep a recoverable copy of the token: a share list that can
|
|
71
|
+
* still show you the URL is a share list that can leak every live link at once.
|
|
72
|
+
* Consumers must treat both fields as optional and drive their copy-link
|
|
73
|
+
* affordance off creation, not off a fetched share.
|
|
74
|
+
*/
|
|
61
75
|
export const ChatShareInfoSchema = z.object({
|
|
62
76
|
id: z.string(),
|
|
63
77
|
chatId: z.string(),
|
|
64
|
-
|
|
78
|
+
/** Create-time only — never returned by list or update (show-once). */
|
|
79
|
+
token: z.string().optional(),
|
|
65
80
|
visibility: ChatVisibilitySchema,
|
|
66
|
-
|
|
81
|
+
/** Create-time only — never returned by list or update (show-once). */
|
|
82
|
+
shareUrl: z.string().optional(),
|
|
67
83
|
messageCountAtShare: z.number().int(),
|
|
68
84
|
titleAtShare: z.string(),
|
|
69
85
|
createdAt: IsoDateString,
|
package/src/content/schemas.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { CursorPageSchema } from "../api/primitives";
|
|
14
14
|
import { EffectiveAccessResponseSchema } from "../permissions/share-api";
|
|
15
|
+
import { ViewerPendingAccessRequestSchema } from "../permissions/access-request";
|
|
15
16
|
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
17
18
|
// Company.md Sub-schemas
|
|
@@ -112,6 +113,15 @@ export const CompanyMdDocResponseSchema = z.object({
|
|
|
112
113
|
.optional(),
|
|
113
114
|
/** Whether the requesting actor may request access (ADR-BE-348 / ADR-BE-338). */
|
|
114
115
|
canRequestAccess: z.boolean().optional(),
|
|
116
|
+
/**
|
|
117
|
+
* The requesting actor's own OPEN request on this node, if any
|
|
118
|
+
* (ADR-CONTRACTS-106). `canRequestAccess` answers "may you ask?" and is
|
|
119
|
+
* derived from discoverability + read-state alone, so it stays true after you
|
|
120
|
+
* have asked — this field is what distinguishes "you may ask" from "you
|
|
121
|
+
* already did", and it is the only thing standing between the locked view and
|
|
122
|
+
* an actor re-submitting a request they already filed.
|
|
123
|
+
*/
|
|
124
|
+
pendingAccessRequest: ViewerPendingAccessRequestSchema.nullable().optional(),
|
|
115
125
|
visibility: z.enum(["private", "unit", "org"]),
|
|
116
126
|
// Governance + discovery projections (ADR-BE-348 / ADR-CTRL-194). Present on
|
|
117
127
|
// the TS contract since the projection split; now promoted into the published
|
|
@@ -11,6 +11,7 @@ exports[`NOTIFICATION_DEFINITIONS > titles every kind 1`] = `
|
|
|
11
11
|
"chat.shared · No preview": "A chat has been shared with you",
|
|
12
12
|
"chat.shared · Short": "A chat has been shared with you",
|
|
13
13
|
"companyMd.access_request_approved · Editor": "Your access request was approved",
|
|
14
|
+
"companyMd.access_request_approved · Editor with message": "Your access request was approved",
|
|
14
15
|
"companyMd.access_request_approved · Viewer": "Your access request was approved",
|
|
15
16
|
"companyMd.access_request_denied · No reason": "Your access request was reviewed",
|
|
16
17
|
"companyMd.access_request_denied · With reason": "Your access request was reviewed",
|
|
@@ -1603,6 +1603,185 @@ This notification was sent via Company Semantics.
|
|
|
1603
1603
|
Access is now active.
|
|
1604
1604
|
|
|
1605
1605
|
|
|
1606
|
+
/* EOM */
|
|
1607
|
+
ⓒ 2026 • Company Semantics
|
|
1608
|
+
https://companysemantics.ai
|
|
1609
|
+
",
|
|
1610
|
+
}
|
|
1611
|
+
`;
|
|
1612
|
+
|
|
1613
|
+
exports[`email render > companyMd.access_request_approved · Editor with message 1`] = `
|
|
1614
|
+
{
|
|
1615
|
+
"amp": "<!DOCTYPE html>
|
|
1616
|
+
<html amp4email data-css-strict lang="en">
|
|
1617
|
+
<head><meta charset="utf-8">
|
|
1618
|
+
<script async src="https://cdn.ampproject.org/v0.js"></script>
|
|
1619
|
+
<style amp4email-boilerplate>body{visibility:hidden}</style>
|
|
1620
|
+
<style amp-custom>.csr-body { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; background-color: #ffffff; margin: 0; padding: 0; }
|
|
1621
|
+
.csr-frame { max-width: 520px; margin: 0 auto; }
|
|
1622
|
+
.csr-p-normal { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0; }
|
|
1623
|
+
.csr-p-tight { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0; }
|
|
1624
|
+
.csr-p-none { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0; }
|
|
1625
|
+
.csr-eom { color: #bbb; }
|
|
1626
|
+
.csr-signature-link { color: #0047FF; text-decoration: none; }
|
|
1627
|
+
.csr-warning { color: #e7000b; }
|
|
1628
|
+
.csr-hr-normal { border: none; border-top: 1px solid #bbb; margin: 0 0 20px 0; }
|
|
1629
|
+
.csr-hr-tight { border: none; border-top: 1px solid #bbb; margin: 0 0 4px 0; }
|
|
1630
|
+
.csr-hr-none { border: none; border-top: 1px solid #bbb; margin: 0; }
|
|
1631
|
+
.csr-hero-normal { max-width: 100%; margin: 0 0 20px 0; }
|
|
1632
|
+
.csr-hero-tight { max-width: 100%; margin: 0 0 4px 0; }
|
|
1633
|
+
.csr-hero-none { max-width: 100%; margin: 0; }
|
|
1634
|
+
.csr-cta-box-normal { display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0 0 20px 0; max-width: 220px; }
|
|
1635
|
+
.csr-cta-box-tight { display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0 0 4px 0; max-width: 220px; }
|
|
1636
|
+
.csr-cta-box-none { display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0; max-width: 220px; }
|
|
1637
|
+
.csr-cta-cell-linked { text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; }
|
|
1638
|
+
.csr-cta-cell-code { padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; }
|
|
1639
|
+
.csr-cta-label { text-decoration: underline; }
|
|
1640
|
+
.csr-cta-anchor { display: block; padding: 16px 24px; color: #0047FF; text-decoration: none; }
|
|
1641
|
+
.csr-cta-code { color: #0047FF; }
|
|
1642
|
+
.csr-chat-rule-top { border: none; border-top: 1px solid #bbb; margin: 12px 0 24px 0; }
|
|
1643
|
+
.csr-chat-rule-bottom { border: none; border-top: 1px solid #bbb; margin: 24px 0 12px 0; }
|
|
1644
|
+
.csr-chat-row-24 { margin: 0 0 24px 0; }
|
|
1645
|
+
.csr-chat-row-16 { margin: 0 0 16px 0; }
|
|
1646
|
+
.csr-chat-attribution { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #666; text-align: right; padding-top: 6px; padding-right: 1ch; }
|
|
1647
|
+
.csr-chat-avatar-left { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; white-space: nowrap; }
|
|
1648
|
+
.csr-chat-avatar-left-hidden { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap; }
|
|
1649
|
+
.csr-chat-avatar-right { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; white-space: nowrap; }
|
|
1650
|
+
.csr-chat-avatar-right-hidden { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap; }
|
|
1651
|
+
.csr-chat-channel-left { width: 100%; text-align: left; }
|
|
1652
|
+
.csr-chat-channel-right { width: 100%; text-align: right; }
|
|
1653
|
+
.csr-chat-bubble-wrap { display: inline-block; max-width: 100%; vertical-align: bottom; }
|
|
1654
|
+
.csr-bubble-user { border-radius: 8px 8px 0 8px; padding: 10px 14px; text-align: right; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666; }
|
|
1655
|
+
.csr-bubble-assistant { border-radius: 8px 8px 8px 0; padding: 10px 14px; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666; }
|
|
1656
|
+
.csr-dots-over-cta { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; font-weight: bold; color: #666; text-align: center; margin: 0 0 16px 0; }
|
|
1657
|
+
.csr-dots-cell { width: 100%; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 20px; font-weight: bold; color: #666; }
|
|
1658
|
+
.csr-cta-stack { display: inline-block; text-align: left; }
|
|
1659
|
+
.cs-cta-hover:hover { background-color: #d1ffff; }</style></head>
|
|
1660
|
+
<body class="csr-body">
|
|
1661
|
+
<div class="csr-frame">
|
|
1662
|
+
<p class="csr-p-normal">Hi there,</p>
|
|
1663
|
+
<p class="csr-p-normal">Your access request was approved.</p>
|
|
1664
|
+
<hr class="cs-faint csr-chat-rule-top">
|
|
1665
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="csr-chat-row-16">
|
|
1666
|
+
<tr>
|
|
1667
|
+
<td class="csr-chat-avatar-left-hidden">[c_S]</td>
|
|
1668
|
+
<td class="csr-chat-channel-right"><table cellpadding="0" cellspacing="0" border="0" class="csr-chat-bubble-wrap">
|
|
1669
|
+
<tr><td class="cs-bubble csr-bubble-user">Welcome aboard — start with the onboarding section.</td></tr>
|
|
1670
|
+
</table></td>
|
|
1671
|
+
<td class="csr-chat-avatar-right">(•̀_ರ╮)</td>
|
|
1672
|
+
</tr>
|
|
1673
|
+
<tr>
|
|
1674
|
+
<td></td>
|
|
1675
|
+
<td class="cs-meta csr-chat-attribution">Jordan Lee</td>
|
|
1676
|
+
<td></td>
|
|
1677
|
+
</tr>
|
|
1678
|
+
</table>
|
|
1679
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="csr-chat-row-24">
|
|
1680
|
+
<tr>
|
|
1681
|
+
<td class="csr-chat-avatar-left-hidden">[c_S]</td>
|
|
1682
|
+
<td class="csr-chat-channel-right"><div class="csr-cta-stack"><table cellpadding="0" cellspacing="0" border="0" class="cs-cta cs-cta-hover csr-cta-box-none">
|
|
1683
|
+
<tr><td class="csr-cta-cell-linked">
|
|
1684
|
+
<a href="https://app.companysemantics.ai/doc/handbook" class="cs-link csr-cta-anchor">>> <span class="csr-cta-label">OPEN</span> <<</a>
|
|
1685
|
+
</td></tr>
|
|
1686
|
+
</table></div></td>
|
|
1687
|
+
<td class="csr-chat-avatar-right-hidden">(•̀_ರ╮)</td>
|
|
1688
|
+
</tr>
|
|
1689
|
+
</table>
|
|
1690
|
+
<hr class="cs-faint csr-chat-rule-bottom">
|
|
1691
|
+
<p class="csr-p-tight">Document: "Engineering Handbook"</p>
|
|
1692
|
+
<p class="csr-p-tight">Access: can edit</p>
|
|
1693
|
+
<p class="csr-p-normal">Approved by: Jordan Lee</p>
|
|
1694
|
+
<p class="csr-p-none">This notification was sent via Company Semantics.<br>Access is now active.</p>
|
|
1695
|
+
<p class="csr-p-none"><br><br><span class="cs-faint csr-eom">/* EOM */</span><br>ⓒ 2026 • Company Semantics<br><a href="https://companysemantics.ai" target="_blank" rel="noopener noreferrer" class="cs-link csr-signature-link">https://companysemantics.ai</a></p>
|
|
1696
|
+
</div>
|
|
1697
|
+
</body>
|
|
1698
|
+
</html>",
|
|
1699
|
+
"html": "<!DOCTYPE html>
|
|
1700
|
+
<html lang="en">
|
|
1701
|
+
<head><meta charset="UTF-8">
|
|
1702
|
+
<meta name="color-scheme" content="light dark">
|
|
1703
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
1704
|
+
<style>.cs-cta-hover:hover { background-color: #d1ffff !important; }
|
|
1705
|
+
@media (prefers-color-scheme: dark) {
|
|
1706
|
+
body { background-color: #1a1a1a !important; color: #e8e8e8 !important; }
|
|
1707
|
+
.cs-faint { color: #666 !important; border-top-color: #666 !important; }
|
|
1708
|
+
.cs-meta { color: #8a8a8a !important; }
|
|
1709
|
+
.cs-cta { border-color: #00ffff !important; }
|
|
1710
|
+
.cs-cta-hover:hover { background-color: #154848 !important; }
|
|
1711
|
+
.cs-bubble { background: #154848 !important; color: #ffffff !important; }
|
|
1712
|
+
.cs-dots { color: #8a8a8a !important; }
|
|
1713
|
+
.cs-link { color: #00ffff !important; }
|
|
1714
|
+
.cs-destructive { color: #ff6b6b !important; }
|
|
1715
|
+
}</style>
|
|
1716
|
+
<!--[if mso]><style>.cs-cta td { padding: 16px 24px !important; }
|
|
1717
|
+
.cs-cta a { padding: 0 !important; }</style><![endif]--></head>
|
|
1718
|
+
<body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; background-color: #ffffff; margin: 0; padding: 0;">
|
|
1719
|
+
<div style="max-width: 520px; margin: 0 auto;">
|
|
1720
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Hi there,</p>
|
|
1721
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Your access request was approved.</p>
|
|
1722
|
+
<hr class="cs-faint" style="border: none; border-top: 1px solid #bbb; margin: 12px 0 24px 0;">
|
|
1723
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin: 0 0 16px 0;">
|
|
1724
|
+
<tr>
|
|
1725
|
+
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">[c_S]</td>
|
|
1726
|
+
<td style="width: 100%; text-align: right;"><table cellpadding="0" cellspacing="0" border="0" style="display: inline-block; max-width: 100%; vertical-align: bottom;">
|
|
1727
|
+
<tr><td class="cs-bubble" style="border-radius: 8px 8px 0 8px; padding: 10px 14px; text-align: right; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #ffffff; background: #666;">Welcome aboard — start with the onboarding section.</td></tr>
|
|
1728
|
+
</table></td>
|
|
1729
|
+
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; white-space: nowrap;">(•̀_ರ╮)</td>
|
|
1730
|
+
</tr>
|
|
1731
|
+
<tr>
|
|
1732
|
+
<td></td>
|
|
1733
|
+
<td class="cs-meta" style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; color: #666; text-align: right; padding-top: 6px; padding-right: 1ch;">Jordan Lee</td>
|
|
1734
|
+
<td></td>
|
|
1735
|
+
</tr>
|
|
1736
|
+
</table>
|
|
1737
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin: 0 0 24px 0;">
|
|
1738
|
+
<tr>
|
|
1739
|
+
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">[c_S]</td>
|
|
1740
|
+
<td style="width: 100%; text-align: right;"><div style="display: inline-block; text-align: left;"><table cellpadding="0" cellspacing="0" border="0" class="cs-cta cs-cta-hover" style="display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0; max-width: 220px;">
|
|
1741
|
+
<tr><td style="text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px;">
|
|
1742
|
+
<a href="https://app.companysemantics.ai/doc/handbook" class="cs-link" style="display: block; padding: 16px 24px; color: #0047FF; text-decoration: none;">>> <span style="text-decoration: underline;">OPEN</span> <<</a>
|
|
1743
|
+
</td></tr>
|
|
1744
|
+
</table></div></td>
|
|
1745
|
+
<td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-left: 8px; vertical-align: bottom; visibility: hidden; white-space: nowrap;">(•̀_ರ╮)</td>
|
|
1746
|
+
</tr>
|
|
1747
|
+
</table>
|
|
1748
|
+
<hr class="cs-faint" style="border: none; border-top: 1px solid #bbb; margin: 24px 0 12px 0;">
|
|
1749
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0;">Document: "Engineering Handbook"</p>
|
|
1750
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0;">Access: can edit</p>
|
|
1751
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Approved by: Jordan Lee</p>
|
|
1752
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0;">This notification was sent via Company Semantics.<br>Access is now active.</p>
|
|
1753
|
+
<p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0;"><br><br><span class="cs-faint" style="color: #bbb;">/* EOM */</span><br>ⓒ 2026 • Company Semantics<br><a href="https://companysemantics.ai" target="_blank" rel="noopener noreferrer" class="cs-link" style="color: #0047FF; text-decoration: none;">https://companysemantics.ai</a></p>
|
|
1754
|
+
</div>
|
|
1755
|
+
</body>
|
|
1756
|
+
</html>",
|
|
1757
|
+
"subject": "Your access request was approved",
|
|
1758
|
+
"text": "Hi there,
|
|
1759
|
+
|
|
1760
|
+
Your access request was approved.
|
|
1761
|
+
|
|
1762
|
+
________________________________________________________
|
|
1763
|
+
|
|
1764
|
+
┌──────────────────────────────────────┐
|
|
1765
|
+
│ Welcome aboard — start with the │
|
|
1766
|
+
│ onboarding section. │ (•̀_ರ╮)
|
|
1767
|
+
└──────────────────────────────────────┘
|
|
1768
|
+
Jordan Lee
|
|
1769
|
+
|
|
1770
|
+
*----------------*
|
|
1771
|
+
| >> OPEN << |
|
|
1772
|
+
*----------------*
|
|
1773
|
+
|
|
1774
|
+
https://app.companysemantics.ai/doc/handbook
|
|
1775
|
+
________________________________________________________
|
|
1776
|
+
|
|
1777
|
+
Document: "Engineering Handbook"
|
|
1778
|
+
Access: can edit
|
|
1779
|
+
Approved by: Jordan Lee
|
|
1780
|
+
|
|
1781
|
+
This notification was sent via Company Semantics.
|
|
1782
|
+
Access is now active.
|
|
1783
|
+
|
|
1784
|
+
|
|
1606
1785
|
/* EOM */
|
|
1607
1786
|
ⓒ 2026 • Company Semantics
|
|
1608
1787
|
https://companysemantics.ai
|
|
@@ -212,6 +212,15 @@ add("companyMd.access_request_approved", "Editor", {
|
|
|
212
212
|
accessLevel: "editor",
|
|
213
213
|
docUrl: `${APP}/doc/handbook`,
|
|
214
214
|
});
|
|
215
|
+
// The approver's note replaces the "Open to view." prompt rather than stacking
|
|
216
|
+
// on it — the branch this variant exists to pin.
|
|
217
|
+
add("companyMd.access_request_approved", "Editor with message", {
|
|
218
|
+
approverName: "Jordan Lee",
|
|
219
|
+
docTitle: "Engineering Handbook",
|
|
220
|
+
accessLevel: "editor",
|
|
221
|
+
docUrl: `${APP}/doc/handbook`,
|
|
222
|
+
message: "Welcome aboard — start with the onboarding section.",
|
|
223
|
+
});
|
|
215
224
|
add("companyMd.access_request_approved", "Viewer", {
|
|
216
225
|
approverName: "Jordan Lee",
|
|
217
226
|
docTitle: "Engineering Handbook",
|
|
@@ -212,6 +212,15 @@ add("companyMd.access_request_approved", "Editor", {
|
|
|
212
212
|
accessLevel: "editor",
|
|
213
213
|
docUrl: `${APP}/doc/handbook`,
|
|
214
214
|
});
|
|
215
|
+
// The approver's note replaces the "Open to view." prompt rather than stacking
|
|
216
|
+
// on it — the branch this variant exists to pin, across every channel.
|
|
217
|
+
add("companyMd.access_request_approved", "Editor with message", {
|
|
218
|
+
approverName: "Jordan Lee",
|
|
219
|
+
docTitle: "Engineering Handbook",
|
|
220
|
+
accessLevel: "editor",
|
|
221
|
+
docUrl: `${APP}/doc/handbook`,
|
|
222
|
+
message: "Welcome aboard — start with the onboarding section.",
|
|
223
|
+
});
|
|
215
224
|
add("companyMd.access_request_approved", "Viewer", {
|
|
216
225
|
approverName: "Jordan Lee",
|
|
217
226
|
docTitle: "Engineering Handbook",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Company.md access-approved notification (sent to the requester).
|
|
3
|
+
*
|
|
4
|
+
* The chat unit carries EITHER the approver's own words or the bare "Open to
|
|
5
|
+
* view." prompt — never both. An approver who wrote a note has already said what
|
|
6
|
+
* the prompt would say, and stacking the two reads as a form letter with a
|
|
7
|
+
* comment bolted on. Same shape as `share.granted` (ADR-CONTRACTS-106); the call
|
|
8
|
+
* to action stays in place either way, since approval always resolves to a doc
|
|
9
|
+
* the requester can now open.
|
|
3
10
|
*/
|
|
4
11
|
|
|
5
12
|
import type { NotificationDefinition } from "../definition";
|
|
@@ -10,7 +17,7 @@ export const accessApprovedDefinition: NotificationDefinition<"companyMd.access_
|
|
|
10
17
|
{
|
|
11
18
|
kind: "companyMd.access_request_approved",
|
|
12
19
|
compose: (payload, context) => {
|
|
13
|
-
const { approverName, docTitle, accessLevel, docUrl } = payload;
|
|
20
|
+
const { approverName, docTitle, accessLevel, docUrl, message } = payload;
|
|
14
21
|
|
|
15
22
|
return {
|
|
16
23
|
metadata: {
|
|
@@ -25,11 +32,18 @@ export const accessApprovedDefinition: NotificationDefinition<"companyMd.access_
|
|
|
25
32
|
{
|
|
26
33
|
type: "chatUnit",
|
|
27
34
|
items: [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
message
|
|
36
|
+
? {
|
|
37
|
+
type: "message",
|
|
38
|
+
role: "user",
|
|
39
|
+
text: message,
|
|
40
|
+
from: approverName,
|
|
41
|
+
}
|
|
42
|
+
: {
|
|
43
|
+
type: "message",
|
|
44
|
+
role: "assistant",
|
|
45
|
+
text: "Open to view.",
|
|
46
|
+
},
|
|
33
47
|
{ type: "callToAction", label: "OPEN", href: docUrl },
|
|
34
48
|
],
|
|
35
49
|
},
|
|
@@ -151,6 +151,12 @@ export interface NotificationPayloads {
|
|
|
151
151
|
accessLevel: "editor" | "commenter" | "viewer";
|
|
152
152
|
/** Full URL to view the node */
|
|
153
153
|
docUrl: string;
|
|
154
|
+
/**
|
|
155
|
+
* Optional note from the approver, included in the notification only (not
|
|
156
|
+
* persisted) — the approval counterpart of the deny kind's `reason`
|
|
157
|
+
* (ADR-CONTRACTS-106).
|
|
158
|
+
*/
|
|
159
|
+
message?: string;
|
|
154
160
|
};
|
|
155
161
|
/** Sent to the requester when an owner denies (ADR-BE-338). */
|
|
156
162
|
"companyMd.access_request_denied": {
|
package/src/org/company-md.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* @see PRD-00173 for design rationale
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { ViewerPendingAccessRequest } from "../permissions/access-request";
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Visibility band for a Company.md document — the canonical AUTH
|
|
11
13
|
* `entity_visibility` vocabulary (ADR-CONT-049, backend ADR-BE-178).
|
|
@@ -218,6 +220,13 @@ export interface CompanyMdDocCore extends CompanyMdNodeIdentity {
|
|
|
218
220
|
* recipient to an `owners` node may still be allowed to request).
|
|
219
221
|
*/
|
|
220
222
|
readonly canRequestAccess?: boolean;
|
|
223
|
+
/**
|
|
224
|
+
* The requesting actor's own OPEN request on this node (ADR-CONTRACTS-106).
|
|
225
|
+
* `canRequestAccess` is derived from discoverability + read-state and stays
|
|
226
|
+
* true once you have asked, so this is the field that separates "you may ask"
|
|
227
|
+
* from "you already did". Absent/`null` ⇒ nothing open.
|
|
228
|
+
*/
|
|
229
|
+
readonly pendingAccessRequest?: ViewerPendingAccessRequest | null;
|
|
221
230
|
/**
|
|
222
231
|
* Whether this doc is served by the real-time collaboration protocol
|
|
223
232
|
* (ADR-CONT-102). When `true` the client may open `/collab/sync` + the collab
|
|
@@ -93,18 +93,89 @@ export const AccessRequestSchema = z.object({
|
|
|
93
93
|
});
|
|
94
94
|
export type AccessRequest = z.infer<typeof AccessRequestSchema>;
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* The requesting actor's OWN open request on a node, surfaced on the single-doc
|
|
98
|
+
* read so a locked view can state that they already asked instead of offering
|
|
99
|
+
* the ask a second time (ADR-CONTRACTS-106).
|
|
100
|
+
*
|
|
101
|
+
* A deliberately NARROWER projection than the owner inbox's
|
|
102
|
+
* {@link AccessRequestSchema}: a requester is not authorized to learn who else
|
|
103
|
+
* asked or how anything was resolved, and needs only what they asked for and
|
|
104
|
+
* when. Absent/`null` ⇒ no open request, which is what an actor who never asked,
|
|
105
|
+
* and an actor whose request was already resolved, both see.
|
|
106
|
+
*
|
|
107
|
+
* Single-doc read ONLY — deliberately not on the tree node schema, where a
|
|
108
|
+
* per-node pending lookup would be an N+1 across the whole tree.
|
|
109
|
+
*/
|
|
110
|
+
export const ViewerPendingAccessRequestSchema = z.object({
|
|
111
|
+
id: z.string(),
|
|
112
|
+
/**
|
|
113
|
+
* The band they asked for. Null for requests filed before the field existed
|
|
114
|
+
* or by a client that named none.
|
|
115
|
+
*/
|
|
116
|
+
requestedAccessLevel: GrantableAccessLevelSchema.nullable(),
|
|
117
|
+
createdAt: z.string(),
|
|
118
|
+
});
|
|
119
|
+
export type ViewerPendingAccessRequest = z.infer<
|
|
120
|
+
typeof ViewerPendingAccessRequestSchema
|
|
121
|
+
>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Character cap on the approver's note to the requester.
|
|
125
|
+
*
|
|
126
|
+
* Deliberately the 2000 the deny `reason` already used, not the requester's 280:
|
|
127
|
+
* the requester writes a one-line ask into an owner's inbox row, the approver
|
|
128
|
+
* writes a reply that is read on its own in an email body.
|
|
129
|
+
*/
|
|
130
|
+
export const ACCESS_DECISION_MESSAGE_MAX_LENGTH = 2000;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Whether the requester is told about the decision.
|
|
134
|
+
*
|
|
135
|
+
* ABSENT MEANS SEND (ADR-CONTRACTS-106). Approve/deny have always emailed the
|
|
136
|
+
* requester unconditionally, so a client that omits the field — an older app, a
|
|
137
|
+
* script — must keep getting that behavior. Only an explicit `false` suppresses
|
|
138
|
+
* the notification; a default of `false` here would silence every existing
|
|
139
|
+
* caller.
|
|
140
|
+
*/
|
|
141
|
+
const NotifySchema = z.boolean().optional();
|
|
142
|
+
|
|
96
143
|
/**
|
|
97
144
|
* POST body to approve a request. The owner chooses the granted level (default
|
|
98
145
|
* `viewer` is applied server-side if the field is omitted by older clients).
|
|
99
146
|
*/
|
|
100
147
|
export const ApproveAccessRequestSchema = z.object({
|
|
101
148
|
accessLevel: GrantableAccessLevelSchema,
|
|
149
|
+
/** Whether to email the requester. Absent ⇒ send (see {@link NotifySchema}). */
|
|
150
|
+
notify: NotifySchema,
|
|
151
|
+
/**
|
|
152
|
+
* Optional note from the approver, quoted to the requester in the approval
|
|
153
|
+
* notification only — never persisted on the request or the minted grant.
|
|
154
|
+
* Ignored when `notify` is false: there is no other surface that would show
|
|
155
|
+
* it. Mirrors `share.granted`'s granter message.
|
|
156
|
+
*/
|
|
157
|
+
message: z
|
|
158
|
+
.string()
|
|
159
|
+
.max(ACCESS_DECISION_MESSAGE_MAX_LENGTH)
|
|
160
|
+
.regex(PLAIN_TEXT, "Message must be plain text")
|
|
161
|
+
.optional(),
|
|
102
162
|
});
|
|
103
163
|
export type ApproveAccessRequest = z.infer<typeof ApproveAccessRequestSchema>;
|
|
104
164
|
|
|
105
165
|
/** POST body to deny a request. */
|
|
106
166
|
export const DenyAccessRequestSchema = z.object({
|
|
107
|
-
/**
|
|
108
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Optional reason, surfaced to the requester (email only). This is the deny
|
|
169
|
+
* verb's message carrier — the approver's composer writes here rather than to
|
|
170
|
+
* a second `message` field, because the denied notification already quotes
|
|
171
|
+
* `reason` and a doc's denial has no other content.
|
|
172
|
+
*/
|
|
173
|
+
reason: z
|
|
174
|
+
.string()
|
|
175
|
+
.max(ACCESS_DECISION_MESSAGE_MAX_LENGTH)
|
|
176
|
+
.regex(PLAIN_TEXT, "Reason must be plain text")
|
|
177
|
+
.optional(),
|
|
178
|
+
/** Whether to email the requester. Absent ⇒ send (see {@link NotifySchema}). */
|
|
179
|
+
notify: NotifySchema,
|
|
109
180
|
});
|
|
110
181
|
export type DenyAccessRequest = z.infer<typeof DenyAccessRequestSchema>;
|
|
@@ -64,8 +64,12 @@ export const AUTH_TOKEN_POLICY_REQUIREMENTS = {
|
|
|
64
64
|
conformance: {
|
|
65
65
|
status: "unmet",
|
|
66
66
|
gap:
|
|
67
|
-
"
|
|
68
|
-
"
|
|
67
|
+
"Issuance now conforms: minted by issueCapabilityToken at 256 bits and " +
|
|
68
|
+
"stored as a sha256:v1: digest (ADR-BE-460 deploy B). Still UNMET because " +
|
|
69
|
+
"the dual-read window is open — sessions.token retains the raw value so a " +
|
|
70
|
+
"rollback keeps working, and every lookup carries a plaintext fallback arm. " +
|
|
71
|
+
"Conformance is reached when migration C retires the column and deploy D " +
|
|
72
|
+
"removes the fallbacks.",
|
|
69
73
|
},
|
|
70
74
|
},
|
|
71
75
|
ChatShareToken: {
|