@company-semantics/contracts 58.0.0 → 58.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "58.0.0",
3
+ "version": "58.1.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -142,7 +142,7 @@
142
142
  "@types/node": "^22.20.1",
143
143
  "amphtml-validator": "^1.0.38",
144
144
  "culori": "^4.0.2",
145
- "eslint": "^10.8.1",
145
+ "eslint": "^10.9.0",
146
146
  "husky": "^9.1.7",
147
147
  "lint-staged": "^17.3.0",
148
148
  "markdownlint-cli2": "^0.23.2",
@@ -151,8 +151,8 @@
151
151
  "tsx": "^4.23.12",
152
152
  "typescript": "^5.8.3",
153
153
  "typescript-eslint": "^8.67.0",
154
- "vite": "^8.2.1",
155
- "vitest": "^4.1.10",
154
+ "vite": "^8.2.2",
155
+ "vitest": "^4.1.11",
156
156
  "yaml": "^2.9.0"
157
157
  },
158
158
  "pnpm": {
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = 'ffeb6e367b38' as const;
3
- export const SPEC_HASH_FULL = 'ffeb6e367b385ad32f7b46e4e74d276786d6cdf175352e57d0fc229dc8f506af' as const;
2
+ export const SPEC_HASH = '49b741e4372d' as const;
3
+ export const SPEC_HASH_FULL = '49b741e4372d56fb1a87312fe62f6e86ffd4987de590d03414ee833a4b6586b0' as const;
@@ -4834,6 +4834,10 @@ export interface components {
4834
4834
  };
4835
4835
  SendInviteBatchRequest: {
4836
4836
  expectedRevision: number;
4837
+ message?: string;
4838
+ roleByPersonId?: {
4839
+ [key: string]: "admin" | "member";
4840
+ };
4837
4841
  };
4838
4842
  InviteValidationResult: {
4839
4843
  valid: boolean;
@@ -5989,6 +5993,14 @@ export interface components {
5989
5993
  /** @enum {string} */
5990
5994
  relationshipType: "solid" | "dotted";
5991
5995
  }[];
5996
+ unplaced: {
5997
+ personId: string;
5998
+ fullName: string;
5999
+ /** @enum {string} */
6000
+ state: "needs_review" | "excluded";
6001
+ /** @enum {string} */
6002
+ reason: "insufficient_evidence" | "conflicting_evidence" | "ambiguous_boundary" | "service_account" | "not_workforce" | "inactive";
6003
+ }[];
5992
6004
  };
5993
6005
  /** @description Polling snapshot of a generic ingestion operation. */
5994
6006
  IngestionOperationPollResponse: {
package/src/index.ts CHANGED
@@ -798,20 +798,72 @@ export type {
798
798
  export {
799
799
  CanonicalPersonSchema,
800
800
  CanonicalOrgUnitSchema,
801
+ CanonicalPositionStatusSchema,
801
802
  CanonicalPositionSchema,
802
803
  CanonicalOccupancySchema,
803
804
  CanonicalReportingEdgeSchema,
805
+ CanonicalUnresolvedManagerResolutionSchema,
806
+ CanonicalUnresolvedManagerSchema,
804
807
  CanonicalFactsSchema,
805
808
  } from "./org/index";
806
809
  export type {
807
810
  CanonicalPerson,
808
811
  CanonicalOrgUnit,
812
+ CanonicalPositionStatus,
809
813
  CanonicalPosition,
810
814
  CanonicalOccupancy,
811
815
  CanonicalReportingEdge,
816
+ CanonicalUnresolvedManagerResolution,
817
+ CanonicalUnresolvedManager,
812
818
  CanonicalFacts,
813
819
  } from "./org/index";
814
820
 
821
+ // Org-structure inference: a StructureInferenceSnapshot of the persisted graph
822
+ // goes in, a StructureProposal — a proposed desired state, never database
823
+ // commands — comes out. Typed evidence, targetUnitId for durable identity, and
824
+ // exactly one PersonStructureOutcome per person in the snapshot. (PRD-00955)
825
+ //
826
+ // Acceptance attaches StructureProvenance — proposalOrigin, acceptedBy and
827
+ // authority as three ORTHOGONAL axes, so accepting an AI proposal never rewrites
828
+ // its origin as manual. mayOverrideAuthority is the single definition of
829
+ // precedence, and it consults `authority` only. (PRD-00956)
830
+ export {
831
+ StructureEvidenceSchema,
832
+ TOPOLOGY_ONLY_EVIDENCE_KINDS,
833
+ StructureUnitAuthoritySchema,
834
+ StructureProposalOriginSchema,
835
+ StructureAcceptedBySchema,
836
+ StructureAuthoritySchema,
837
+ StructureProvenanceSchema,
838
+ mayOverrideAuthority,
839
+ ProposedOrgUnitSchema,
840
+ PersonStructureOutcomeSchema,
841
+ StructureReviewItemSchema,
842
+ StructureProposalSchema,
843
+ StructurePersonFactSchema,
844
+ StructureReportingFactSchema,
845
+ ExistingUnitFactSchema,
846
+ ExistingPlacementFactSchema,
847
+ StructureInferenceSnapshotSchema,
848
+ } from "./org/index";
849
+ export type {
850
+ StructureEvidence,
851
+ StructureUnitAuthority,
852
+ StructureProposalOrigin,
853
+ StructureAcceptedBy,
854
+ StructureAuthority,
855
+ StructureProvenance,
856
+ ProposedOrgUnit,
857
+ PersonStructureOutcome,
858
+ StructureReviewItem,
859
+ StructureProposal,
860
+ StructurePersonFact,
861
+ StructureReportingFact,
862
+ ExistingUnitFact,
863
+ ExistingPlacementFact,
864
+ StructureInferenceSnapshot,
865
+ } from "./org/index";
866
+
815
867
  // Authority & Delegation vocabulary
816
868
  export {
817
869
  AuthoritySourceSchema,
@@ -11,6 +11,7 @@ exports[`monospace budget > overhangs only where content is atomic 1`] = `
11
11
  "https://app.companysemantics.ai/doc/handbook?thread=th_456",
12
12
  "https://app.companysemantics.ai/invite/abc123",
13
13
  "https://app.companysemantics.ai/invite/def456",
14
+ "https://app.companysemantics.ai/invite/ghi789",
14
15
  "https://app.companysemantics.ai/meeting/8842",
15
16
  "https://app.companysemantics.ai/org/unit/42",
16
17
  "https://app.companysemantics.ai/owner-transfer/accept/tok123",
@@ -24,6 +24,7 @@ exports[`NOTIFICATION_DEFINITIONS > titles every kind 1`] = `
24
24
  "companyMd.access_requested · With message": "Someone requested access to a document",
25
25
  "org.invite · Admin": "You've been invited to join Acme Corp on Company Semantics",
26
26
  "org.invite · Member": "You've been invited to join Acme Corp on Company Semantics",
27
+ "org.invite · Member + note": "You've been invited to join Acme Corp on Company Semantics",
27
28
  "org.ownership_transfer · No note (admin)": "You've been invited to become a workspace owner",
28
29
  "org.ownership_transfer · With note + from": "You've been invited to become a workspace owner",
29
30
  "org.ownership_transfer_completed · Default": "Workspace ownership has been transferred",
@@ -3481,6 +3481,213 @@ If you weren't expecting this, no action is
3481
3481
  required.
3482
3482
 
3483
3483
 
3484
+ /* EOM */
3485
+ ⓒ 2026 • Company Semantics
3486
+ https://companysemantics.ai
3487
+ ",
3488
+ }
3489
+ `;
3490
+
3491
+ exports[`email render > org.invite · Member + note 1`] = `
3492
+ {
3493
+ "amp": "<!DOCTYPE html>
3494
+ <html amp4email data-css-strict lang="en">
3495
+ <head><meta charset="utf-8">
3496
+ <script async src="https://cdn.ampproject.org/v0.js"></script>
3497
+ <style amp4email-boilerplate>body{visibility:hidden}</style>
3498
+ <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; }
3499
+ .csr-frame { max-width: 520px; margin: 0 auto; }
3500
+ .csr-p-normal { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0; }
3501
+ .csr-p-tight { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0; }
3502
+ .csr-p-none { font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0; }
3503
+ .csr-eom { color: #bbb; }
3504
+ .csr-signature-link { color: #0047FF; text-decoration: none; }
3505
+ .csr-warning { color: #e7000b; }
3506
+ .csr-hr-normal { border: none; border-top: 1px solid #bbb; margin: 0 0 20px 0; }
3507
+ .csr-hr-tight { border: none; border-top: 1px solid #bbb; margin: 0 0 4px 0; }
3508
+ .csr-hr-none { border: none; border-top: 1px solid #bbb; margin: 0; }
3509
+ .csr-hero-normal { max-width: 100%; margin: 0 0 20px 0; }
3510
+ .csr-hero-tight { max-width: 100%; margin: 0 0 4px 0; }
3511
+ .csr-hero-none { max-width: 100%; margin: 0; }
3512
+ .csr-cta-box-normal { display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0 0 20px 0; max-width: 220px; }
3513
+ .csr-cta-box-tight { display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0 0 4px 0; max-width: 220px; }
3514
+ .csr-cta-box-none { display: inline-block; border: 1px solid #0047FF; border-radius: 2px; margin: 0; max-width: 220px; }
3515
+ .csr-cta-cell-linked { text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; }
3516
+ .csr-cta-cell-code { padding: 16px 24px; text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; }
3517
+ .csr-cta-label { text-decoration: underline; }
3518
+ .csr-cta-anchor { display: block; padding: 16px 24px; color: #0047FF; text-decoration: none; }
3519
+ .csr-cta-code { color: #0047FF; }
3520
+ .csr-chat-rule-top { border: none; border-top: 1px solid #bbb; margin: 12px 0 24px 0; }
3521
+ .csr-chat-rule-bottom { border: none; border-top: 1px solid #bbb; margin: 24px 0 12px 0; }
3522
+ .csr-chat-row-24 { margin: 0 0 24px 0; }
3523
+ .csr-chat-row-16 { margin: 0 0 16px 0; }
3524
+ .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; }
3525
+ .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; }
3526
+ .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; }
3527
+ .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; }
3528
+ .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; }
3529
+ .csr-chat-channel-left { width: 100%; text-align: left; }
3530
+ .csr-chat-channel-right { width: 100%; text-align: right; }
3531
+ .csr-chat-bubble-wrap { display: inline-block; max-width: 100%; vertical-align: bottom; }
3532
+ .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; }
3533
+ .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; }
3534
+ .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; }
3535
+ .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; }
3536
+ .csr-cta-stack { display: inline-block; text-align: left; }
3537
+ .cs-cta-hover:hover { background-color: #d1ffff; }</style></head>
3538
+ <body class="csr-body">
3539
+ <div class="csr-frame">
3540
+ <p class="csr-p-normal">Hi there,</p>
3541
+ <p class="csr-p-normal">Acme Corp uses Company Semantics.</p>
3542
+ <hr class="cs-faint csr-chat-rule-top">
3543
+ <table cellpadding="0" cellspacing="0" border="0" width="100%" class="csr-chat-row-24">
3544
+ <tr>
3545
+ <td class="csr-chat-avatar-left-hidden">[c_S]</td>
3546
+ <td class="csr-chat-channel-right"><table cellpadding="0" cellspacing="0" border="0" class="csr-chat-bubble-wrap">
3547
+ <tr><td class="cs-bubble csr-bubble-user">We start planning next quarter on Monday — glad you are joining.</td></tr>
3548
+ </table></td>
3549
+ <td class="csr-chat-avatar-right">(•̀_ರ╮)</td>
3550
+ </tr>
3551
+ <tr>
3552
+ <td></td>
3553
+ <td class="cs-meta csr-chat-attribution">Alex Rivera</td>
3554
+ <td></td>
3555
+ </tr>
3556
+ </table>
3557
+ <table cellpadding="0" cellspacing="0" border="0" width="100%" class="csr-chat-row-16">
3558
+ <tr>
3559
+ <td class="csr-chat-avatar-left">[c_S]</td>
3560
+ <td class="csr-chat-channel-left"><table cellpadding="0" cellspacing="0" border="0" class="csr-chat-bubble-wrap">
3561
+ <tr><td class="cs-bubble csr-bubble-assistant">Join to accept the invitation.</td></tr>
3562
+ </table></td>
3563
+ <td class="csr-chat-avatar-right-hidden">(•̀_ರ╮)</td>
3564
+ </tr>
3565
+ </table>
3566
+ <table cellpadding="0" cellspacing="0" border="0" width="100%" class="csr-chat-row-24">
3567
+ <tr>
3568
+ <td class="csr-chat-avatar-left-hidden">[c_S]</td>
3569
+ <td class="csr-chat-channel-left"><div class="csr-cta-stack"><table cellpadding="0" cellspacing="0" border="0" class="cs-cta cs-cta-hover csr-cta-box-none">
3570
+ <tr><td class="csr-cta-cell-linked">
3571
+ <a href="https://app.companysemantics.ai/invite/ghi789" class="cs-link csr-cta-anchor">&gt;&gt; <span class="csr-cta-label">JOIN</span> &lt;&lt;</a>
3572
+ </td></tr>
3573
+ </table></div></td>
3574
+ <td class="csr-chat-avatar-right-hidden">(•̀_ರ╮)</td>
3575
+ </tr>
3576
+ </table>
3577
+ <hr class="cs-faint csr-chat-rule-bottom">
3578
+ <p class="csr-p-tight">From: Alex Rivera</p>
3579
+ <p class="csr-p-tight">Workspace: Acme Corp</p>
3580
+ <p class="csr-p-tight">Role: Member</p>
3581
+ <p class="csr-p-normal">Expires: Jun 13, 2026</p>
3582
+ <p class="csr-p-none">This invitation was sent via Company Semantics.<br>If you weren&#39;t expecting this, no action is required.</p>
3583
+ <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>
3584
+ </div>
3585
+ </body>
3586
+ </html>",
3587
+ "html": "<!DOCTYPE html>
3588
+ <html lang="en">
3589
+ <head><meta charset="UTF-8">
3590
+ <meta name="color-scheme" content="light dark">
3591
+ <meta name="supported-color-schemes" content="light dark">
3592
+ <style>.cs-cta-hover:hover { background-color: #d1ffff !important; }
3593
+ @media (prefers-color-scheme: dark) {
3594
+ body { background-color: #1a1a1a !important; color: #e8e8e8 !important; }
3595
+ .cs-faint { color: #666 !important; border-top-color: #666 !important; }
3596
+ .cs-meta { color: #8a8a8a !important; }
3597
+ .cs-cta { border-color: #00ffff !important; }
3598
+ .cs-cta-hover:hover { background-color: #154848 !important; }
3599
+ .cs-bubble { background: #154848 !important; color: #ffffff !important; }
3600
+ .cs-dots { color: #8a8a8a !important; }
3601
+ .cs-link { color: #00ffff !important; }
3602
+ .cs-destructive { color: #ff6b6b !important; }
3603
+ }</style>
3604
+ <!--[if mso]><style>.cs-cta td { padding: 16px 24px !important; }
3605
+ .cs-cta a { padding: 0 !important; }</style><![endif]--></head>
3606
+ <body style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; color: #1a1a1a; background-color: #ffffff; margin: 0; padding: 0;">
3607
+ <div style="max-width: 520px; margin: 0 auto;">
3608
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Hi there,</p>
3609
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Acme Corp uses Company Semantics.</p>
3610
+ <hr class="cs-faint" style="border: none; border-top: 1px solid #bbb; margin: 12px 0 24px 0;">
3611
+ <table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin: 0 0 24px 0;">
3612
+ <tr>
3613
+ <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>
3614
+ <td style="width: 100%; text-align: right;"><table cellpadding="0" cellspacing="0" border="0" style="display: inline-block; max-width: 100%; vertical-align: bottom;">
3615
+ <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;">We start planning next quarter on Monday — glad you are joining.</td></tr>
3616
+ </table></td>
3617
+ <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>
3618
+ </tr>
3619
+ <tr>
3620
+ <td></td>
3621
+ <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;">Alex Rivera</td>
3622
+ <td></td>
3623
+ </tr>
3624
+ </table>
3625
+ <table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin: 0 0 16px 0;">
3626
+ <tr>
3627
+ <td style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; padding-right: 8px; vertical-align: bottom; white-space: nowrap;">[c_S]</td>
3628
+ <td style="width: 100%; text-align: left;"><table cellpadding="0" cellspacing="0" border="0" style="display: inline-block; max-width: 100%; vertical-align: bottom;">
3629
+ <tr><td class="cs-bubble" style="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;">Join to accept the invitation.</td></tr>
3630
+ </table></td>
3631
+ <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>
3632
+ </tr>
3633
+ </table>
3634
+ <table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin: 0 0 24px 0;">
3635
+ <tr>
3636
+ <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>
3637
+ <td style="width: 100%; text-align: left;"><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;">
3638
+ <tr><td style="text-align: center; font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px;">
3639
+ <a href="https://app.companysemantics.ai/invite/ghi789" class="cs-link" style="display: block; padding: 16px 24px; color: #0047FF; text-decoration: none;">&gt;&gt; <span style="text-decoration: underline;">JOIN</span> &lt;&lt;</a>
3640
+ </td></tr>
3641
+ </table></div></td>
3642
+ <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>
3643
+ </tr>
3644
+ </table>
3645
+ <hr class="cs-faint" style="border: none; border-top: 1px solid #bbb; margin: 24px 0 12px 0;">
3646
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0;">From: Alex Rivera</p>
3647
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0;">Workspace: Acme Corp</p>
3648
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 4px 0;">Role: Member</p>
3649
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0 0 20px 0;">Expires: Jun 13, 2026</p>
3650
+ <p style="font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; margin: 0;">This invitation was sent via Company Semantics.<br>If you weren&#39;t expecting this, no action is required.</p>
3651
+ <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>
3652
+ </div>
3653
+ </body>
3654
+ </html>",
3655
+ "subject": "You've been invited to join Acme Corp on Company Semantics",
3656
+ "text": "Hi there,
3657
+
3658
+ Acme Corp uses Company Semantics.
3659
+
3660
+ ────────────────────────────────────────────────
3661
+
3662
+ ┌──────────────────────────────┐
3663
+ │ We start planning next │
3664
+ │ quarter on Monday — glad you │
3665
+ │ are joining. │ (•̀_ರ╮)
3666
+ └──────────────────────────────┘
3667
+ Alex Rivera
3668
+
3669
+ ┌──────────────────────────────┐
3670
+ │ Join to accept the │
3671
+ [c_S] │ invitation. │
3672
+ └──────────────────────────────┘
3673
+
3674
+ *----------------*
3675
+ | >> JOIN << |
3676
+ *----------------*
3677
+
3678
+ https://app.companysemantics.ai/invite/ghi789
3679
+ ────────────────────────────────────────────────
3680
+
3681
+ From: Alex Rivera
3682
+ Workspace: Acme Corp
3683
+ Role: Member
3684
+ Expires: Jun 13, 2026
3685
+
3686
+ This invitation was sent via Company Semantics.
3687
+ If you weren't expecting this, no action is
3688
+ required.
3689
+
3690
+
3484
3691
  /* EOM */
3485
3692
  ⓒ 2026 • Company Semantics
3486
3693
  https://companysemantics.ai
@@ -86,6 +86,15 @@ add("org.invite", "Member", {
86
86
  expiresAt: "2026-06-13T00:00:00.000Z",
87
87
  });
88
88
 
89
+ add("org.invite", "Member + note", {
90
+ inviterName: "Alex Rivera",
91
+ orgName: "Acme Corp",
92
+ role: "member",
93
+ acceptUrl: `${APP}/invite/ghi789`,
94
+ expiresAt: "2026-06-13T00:00:00.000Z",
95
+ message: "We start planning next quarter on Monday — glad you are joining.",
96
+ });
97
+
89
98
  add("org.unit_owner_granted", "Unit owner + message", {
90
99
  granterName: "Jordan Lee",
91
100
  recipientName: "Sam Chen",
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The invitation's optional note.
3
+ *
4
+ * An invite is often the first thing someone ever receives from a workspace, so
5
+ * the one line of human context on it does real work — and it has to arrive
6
+ * ATTRIBUTED. A note rendered as product copy reads as boilerplate, which is
7
+ * the one thing it is not.
8
+ */
9
+
10
+ import { describe, expect, it } from "vitest";
11
+
12
+ import { createRenderContext } from "../context";
13
+ import type { ChatUnitItem, NotificationContent } from "../content";
14
+ import { orgInviteDefinition } from "../kinds/org-invite";
15
+ import type { NotificationPayloads } from "../payloads";
16
+
17
+ const CONTEXT = createRenderContext();
18
+
19
+ const payload = (
20
+ over: Partial<NotificationPayloads["org.invite"]> = {},
21
+ ): NotificationPayloads["org.invite"] => ({
22
+ inviterName: "Alex Rivera",
23
+ orgName: "Acme Corp",
24
+ role: "member",
25
+ acceptUrl: "https://app.companysemantics.ai/invite/abc123",
26
+ expiresAt: "2026-06-13T00:00:00.000Z",
27
+ ...over,
28
+ });
29
+
30
+ function chatItems(content: NotificationContent): ChatUnitItem[] {
31
+ const elements = content.sections.flatMap((s) => s.elements);
32
+ const unit = elements.find((e) => e.type === "chatUnit");
33
+ if (!unit || unit.type !== "chatUnit") throw new Error("no chat unit");
34
+ return [...unit.items];
35
+ }
36
+
37
+ describe("org.invite", () => {
38
+ it("carries the note as the inviter's own words, above the join prompt", () => {
39
+ const items = chatItems(
40
+ orgInviteDefinition.compose(
41
+ payload({ message: "Glad you are joining." }),
42
+ CONTEXT,
43
+ ),
44
+ );
45
+ expect(items[0]).toEqual({
46
+ type: "message",
47
+ role: "user",
48
+ text: "Glad you are joining.",
49
+ from: "Alex Rivera",
50
+ });
51
+ // The prompt survives the note: the message is WHY someone was invited, the
52
+ // prompt is what to do about it.
53
+ expect(items[1]).toMatchObject({
54
+ role: "assistant",
55
+ text: "Join to accept the invitation.",
56
+ });
57
+ });
58
+
59
+ it("renders no message block at all without one", () => {
60
+ const items = chatItems(orgInviteDefinition.compose(payload(), CONTEXT));
61
+ expect(items.some((i) => i.type === "message" && i.role === "user")).toBe(
62
+ false,
63
+ );
64
+ expect(items[0]).toMatchObject({ role: "assistant" });
65
+ });
66
+
67
+ it("keeps the call to action last either way", () => {
68
+ for (const message of [undefined, "Welcome."]) {
69
+ const items = chatItems(
70
+ orgInviteDefinition.compose(payload({ message }), CONTEXT),
71
+ );
72
+ expect(items[items.length - 1]).toMatchObject({ type: "callToAction" });
73
+ }
74
+ });
75
+ });
@@ -92,6 +92,14 @@ add("org.invite", "Member", {
92
92
  acceptUrl: `${APP}/invite/def456`,
93
93
  expiresAt: "2026-06-13T00:00:00.000Z",
94
94
  });
95
+ add("org.invite", "Member + note", {
96
+ inviterName: "Alex Rivera",
97
+ orgName: "Acme Corp",
98
+ role: "member",
99
+ acceptUrl: `${APP}/invite/ghi789`,
100
+ expiresAt: "2026-06-13T00:00:00.000Z",
101
+ message: "We start planning next quarter on Monday — glad you are joining.",
102
+ });
95
103
 
96
104
  add("org.unit_owner_granted", "Unit owner + message", {
97
105
  granterName: "Jordan Lee",
@@ -7,6 +7,7 @@
7
7
  * nothing left to catch. See ADR-CONTRACTS-085.
8
8
  */
9
9
 
10
+ import type { ChatUnitItem } from "../content";
10
11
  import type { NotificationDefinition } from "../definition";
11
12
 
12
13
  import { formatExpiry, NOTICE, titleCase } from "../text";
@@ -14,7 +15,31 @@ import { formatExpiry, NOTICE, titleCase } from "../text";
14
15
  export const orgInviteDefinition: NotificationDefinition<"org.invite"> = {
15
16
  kind: "org.invite",
16
17
  compose: (payload, context) => {
17
- const { inviterName, orgName, role, acceptUrl, expiresAt } = payload;
18
+ const { inviterName, orgName, role, acceptUrl, expiresAt, message } =
19
+ payload;
20
+
21
+ // The inviter's own words come FIRST and keep their name on them, above the
22
+ // prompt to join — a note attributed to nobody reads as product copy, which
23
+ // is the one thing it is not. The join prompt stays either way: the message
24
+ // is why this person was invited, the prompt is what to do about it.
25
+ const items: ChatUnitItem[] = [
26
+ ...(message
27
+ ? [
28
+ {
29
+ type: "message" as const,
30
+ role: "user" as const,
31
+ text: message,
32
+ from: inviterName,
33
+ },
34
+ ]
35
+ : []),
36
+ {
37
+ type: "message",
38
+ role: "assistant",
39
+ text: "Join to accept the invitation.",
40
+ },
41
+ { type: "callToAction", label: "JOIN", href: acceptUrl },
42
+ ];
18
43
 
19
44
  return {
20
45
  metadata: {
@@ -26,17 +51,7 @@ export const orgInviteDefinition: NotificationDefinition<"org.invite"> = {
26
51
  elements: [
27
52
  { type: "greeting" },
28
53
  { type: "body", text: `${orgName} uses Company Semantics.` },
29
- {
30
- type: "chatUnit",
31
- items: [
32
- {
33
- type: "message",
34
- role: "assistant",
35
- text: "Join to accept the invitation.",
36
- },
37
- { type: "callToAction", label: "JOIN", href: acceptUrl },
38
- ],
39
- },
54
+ { type: "chatUnit", items },
40
55
  {
41
56
  type: "keyValueTable",
42
57
  rows: [
@@ -51,6 +51,13 @@ export interface NotificationPayloads {
51
51
  acceptUrl: string;
52
52
  /** ISO timestamp when the invitation expires. Rendered as "Expires: Jun 13, 2026". */
53
53
  expiresAt: string;
54
+ /**
55
+ * Optional note from the inviter, shown above the join prompt and attributed
56
+ * to them — the same shape `share.granted` and `org.unit_owner_granted` give
57
+ * a granter's message. Absent means no message block at all, never an empty
58
+ * one.
59
+ */
60
+ message?: string;
54
61
  };
55
62
  "org.unit_owner_granted": {
56
63
  /** Display name of the person who granted access */
package/src/org/README.md CHANGED
@@ -61,8 +61,14 @@ Shared type vocabulary for organization ownership, type classification, and tran
61
61
  - `CanonicalPersonSchema` — A person as reported by an external source, keyed by external identity.
62
62
  - `CanonicalPosition` _(type)_
63
63
  - `CanonicalPositionSchema` — A position (seat) as reported by an external source, placed in a unit by external reference…
64
+ - `CanonicalPositionStatus` _(type)_
65
+ - `CanonicalPositionStatusSchema` — Seat-existence as the SOURCE reported it.
64
66
  - `CanonicalReportingEdge` _(type)_
65
67
  - `CanonicalReportingEdgeSchema` — A reporting edge (a seat reports to a manager seat) as reported by an external source.
68
+ - `CanonicalUnresolvedManager` _(type)_
69
+ - `CanonicalUnresolvedManagerResolution` _(type)_
70
+ - `CanonicalUnresolvedManagerResolutionSchema` — Why a supervisory reference could not be resolved to a seat in this batch.
71
+ - `CanonicalUnresolvedManagerSchema` — A supervisory reference the source reported that we could NOT resolve to a seat in this batch.
66
72
  - `CapabilitiesResponse` _(type)_
67
73
  - `CapabilitiesResponseSchema` — The single validated capability surface the UI reads.
68
74
  - `ChangeMemberRoleRequest` _(type)_ — Request payload for changing a member's role.
@@ -129,6 +135,10 @@ Shared type vocabulary for organization ownership, type classification, and tran
129
135
  - `ExecutionContext` _(type)_
130
136
  - `ExecutionContextSchema` — ExecutionContext — the RUN in which an intent was produced or applied.
131
137
  - `ExecutionScope` _(type)_ — Execution scope determines whose identity is used when executing actions. - 'self': Actions execute under the…
138
+ - `ExistingPlacementFact` _(type)_
139
+ - `ExistingPlacementFactSchema` — Where a person already sits, and on whose authority they sit there.
140
+ - `ExistingUnitFact` _(type)_
141
+ - `ExistingUnitFactSchema` — An org unit that already exists, with the license the engine has over it.
132
142
  - `FACT_SOURCE_TIER_PRECEDENCE` — Truth hierarchy, highest precedence first.
133
143
  - `FactMutation` _(type)_
134
144
  - `FactMutationSchema` — A single atomic fact change the engine applies to the live org graph.
@@ -301,6 +311,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
301
311
  - `OwnershipTransferResponseSchema`
302
312
  - `OwnershipTransferStatus` _(type)_ — Status of pending ownership transfer for an organization.
303
313
  - `PermissionAuditEntry` _(type)_ — Single entry in the permission change audit log.
314
+ - `PersonStructureOutcome` _(type)_
315
+ - `PersonStructureOutcomeSchema` — EXACTLY ONE outcome per person in the snapshot. "Zero or one placement" would accept a proposal that simply…
304
316
  - `Phase3AuditAction` _(type)_ — Audit actions for Phase 3 workspace expansion features.
305
317
  - `Phase4AuditAction` _(type)_ — Audit actions for Phase 4 enterprise identity features.
306
318
  - `Position` _(type)_
@@ -311,6 +323,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
311
323
  - `PositionSchema`
312
324
  - `PositionStatus` _(type)_
313
325
  - `PositionStatusSchema` — Lifecycle of a seat's EXISTENCE — distinct from a hiring pipeline.
326
+ - `ProposedOrgUnit` _(type)_
327
+ - `ProposedOrgUnitSchema` — A unit the proposal wants the org to have.
314
328
  - `Provenance` _(type)_
315
329
  - `ProvenanceSchema` — Provenance — WHO/WHAT generated an intent.
316
330
  - `ProviderStatus` _(type)_ — Provider-level configuration lifecycle.
@@ -351,10 +365,33 @@ Shared type vocabulary for organization ownership, type classification, and tran
351
365
  - `SsoStepperStep` _(type)_ — Backend-authoritative stepper step.
352
366
  - `StagedPlacement` _(type)_ — Staged member placement: the fuller org position an invitee is slotted into, applied as deferred grants when…
353
367
  - `StagedPlacementSchema` — Staged member placement: the fuller org position an invitee is slotted into, applied as deferred grants when…
368
+ - `StructureAcceptedBy` _(type)_
369
+ - `StructureAcceptedBySchema` — WHO accepted the fact.
370
+ - `StructureAuthority` _(type)_
371
+ - `StructureAuthoritySchema` — The authority axis — the ONLY axis precedence runs on: `human_confirmed` `ai_inferred` `hris_source`.
372
+ - `StructureEvidence` _(type)_
373
+ - `StructureEvidenceSchema` — TYPED evidence.
374
+ - `StructureInferenceSnapshot` _(type)_
375
+ - `StructureInferenceSnapshotSchema` — The inference INPUT: a point-in-time view of the PERSISTED canonical graph.
376
+ - `StructurePersonFact` _(type)_
377
+ - `StructurePersonFactSchema` — A person as the inference engine sees them.
378
+ - `StructureProposal` _(type)_
379
+ - `StructureProposalOrigin` _(type)_
380
+ - `StructureProposalOriginSchema` — How a structural fact came to be PROPOSED.
381
+ - `StructureProposalSchema` — A proposed desired state for one org.
382
+ - `StructureProvenance` _(type)_
383
+ - `StructureProvenanceSchema` — The three axes together, as one accepted structural fact records them. - v1 — AI proposes, an admin clicks…
384
+ - `StructureReportingFact` _(type)_
385
+ - `StructureReportingFactSchema` — One reporting edge, at person granularity.
386
+ - `StructureReviewItem` _(type)_
387
+ - `StructureReviewItemSchema` — A question the engine is asking a human, with the competing signals attached.
388
+ - `StructureUnitAuthority` _(type)_
389
+ - `StructureUnitAuthoritySchema` — How much license the engine has over an EXISTING unit or placement.
354
390
  - `SubmitInteractiveTaskResponse` _(type)_
355
391
  - `SubmitInteractiveTaskResponseSchema` — Response for submitting a filled-in interactive chat task surface (e.g. the editable "change reporting…
356
392
  - `SyncRunSummary` _(type)_
357
393
  - `SyncRunSummarySchema`
394
+ - `TOPOLOGY_ONLY_EVIDENCE_KINDS` — Evidence kinds that alone are NOT sufficient to justify a unit boundary.
358
395
  - `TRANSFER_RESPONSIBILITIES` — Responsibility checklist items shown on the acceptance page.
359
396
  - `TestSsoInitiation` _(type)_ — Initiation payload for a test SSO login attempt.
360
397
  - `TestSsoInitiationDto` _(type)_ — Initiation payload for a test SSO login attempt.
@@ -424,6 +461,7 @@ Shared type vocabulary for organization ownership, type classification, and tran
424
461
  - `WorkspaceSsoState` _(type)_ — Workspace SSO state derived from provider status + policy.
425
462
  - `WorkspaceSurface` _(type)_ — Discriminator for workspace surface routing. - 'info': Read-only surface, any member, org.view capabilities -…
426
463
  - `getViewScope` — Get the required scope for a view, or null if public.
464
+ - `mayOverrideAuthority` — True when `incoming` may overwrite a fact currently held at `existing`.
427
465
  - `orderTreeNodes`
428
466
 
429
467
  <!-- END GENERATED: readme-public-api -->