@company-semantics/contracts 60.0.0 → 62.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "60.0.0",
3
+ "version": "62.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = '4ee1bc3befbf' as const;
3
- export const SPEC_HASH_FULL = '4ee1bc3befbf8bb865b4bdad38b892c59f198fec45acdcdde93a067a678516b3' as const;
2
+ export const SPEC_HASH = '5b50d2608eb0' as const;
3
+ export const SPEC_HASH_FULL = '5b50d2608eb090a8a020d1219d8f3ba9d99f825c72fd5f5695f9d2542f32b8a3' as const;
@@ -4263,8 +4263,28 @@ export interface components {
4263
4263
  reason: "external-mutation" | "bulk-operation" | "sync-required";
4264
4264
  };
4265
4265
  AiChatRequest: {
4266
+ /**
4267
+ * @default submit-message
4268
+ * @constant
4269
+ */
4270
+ trigger: "submit-message";
4271
+ id?: string;
4272
+ messageId?: string;
4273
+ message?: {
4274
+ id: string;
4275
+ /** @constant */
4276
+ role: "user";
4277
+ parts: unknown[];
4278
+ };
4266
4279
  messages: unknown[];
4280
+ runtimeProfile?: string;
4281
+ } | {
4282
+ /** @constant */
4283
+ trigger: "regenerate-message";
4267
4284
  id?: string;
4285
+ messageId?: string;
4286
+ message?: unknown;
4287
+ messages: unknown[];
4268
4288
  runtimeProfile?: string;
4269
4289
  };
4270
4290
  ProactiveChatResolution: {
@@ -5628,6 +5648,7 @@ export interface components {
5628
5648
  hasChildren: boolean;
5629
5649
  memberCount: number;
5630
5650
  openRoleCount: number;
5651
+ placedPeopleCount: number;
5631
5652
  missingAtNextLevel: {
5632
5653
  count: number;
5633
5654
  userIds: string[];
@@ -5776,6 +5797,7 @@ export interface components {
5776
5797
  hasChildren: boolean;
5777
5798
  memberCount: number;
5778
5799
  openRoleCount: number;
5800
+ placedPeopleCount: number;
5779
5801
  missingAtNextLevel: {
5780
5802
  count: number;
5781
5803
  userIds: string[];
@@ -71,7 +71,29 @@ export type DecisionEffect =
71
71
  * derivation to run again. Carries the reason so the follow-up has something
72
72
  * to act on, and so the reviewer is not told "saved" when nothing moved.
73
73
  */
74
- | { readonly kind: "needs_reinference"; readonly note: string };
74
+ | { readonly kind: "needs_reinference"; readonly note: string }
75
+ /**
76
+ * Mark a person as the head of a DURABLE unit, and optionally set that unit's
77
+ * owner label. Today the only producer names the org root, so accepting is how
78
+ * an imported chart's CEO becomes the workspace's root owner.
79
+ *
80
+ * DURABLE addressing, not a temp id: post-apply every unit is durable, so a
81
+ * real id is the general form. A temp id would serve only corrections to
82
+ * not-yet-created units, which a proposal's own `headPersonId` already covers
83
+ * with no effect at all.
84
+ *
85
+ * THIS EFFECT NEVER MINTS AUTHORITY. It writes a presentation/semantic marker
86
+ * and a label. Granting unit or account ownership stays with the human-gated
87
+ * ownership route — a root-unit owner IS an account owner, so an automated
88
+ * grant would be a privilege-escalation path.
89
+ */
90
+ | {
91
+ readonly kind: "set_unit_head";
92
+ readonly unitId: string;
93
+ readonly personId: string;
94
+ /** Non-null also sets the unit's owner label; null marks the head only. */
95
+ readonly title: string | null;
96
+ };
75
97
 
76
98
  /** The closed set of effect kinds, as runtime data for validators. */
77
99
  export const DECISION_EFFECT_KINDS = [
@@ -81,6 +103,7 @@ export const DECISION_EFFECT_KINDS = [
81
103
  "unadopt_unit",
82
104
  "archive_unit",
83
105
  "needs_reinference",
106
+ "set_unit_head",
84
107
  ] as const satisfies ReadonlyArray<DecisionEffect["kind"]>;
85
108
 
86
109
  /** One answer a human may give, with what it means and what it does. */
@@ -97,6 +97,7 @@ describe("OrgUnitTreeNodeSchema", () => {
97
97
  hasChildren: true,
98
98
  memberCount: 5,
99
99
  openRoleCount: 1,
100
+ placedPeopleCount: 5,
100
101
  missingAtNextLevel: null,
101
102
  };
102
103
  expect(() => OrgUnitTreeNodeSchema.parse(base)).not.toThrow();
@@ -111,6 +112,7 @@ describe("OrgUnitTreeNodeSchema", () => {
111
112
  hasChildren: true,
112
113
  memberCount: 5,
113
114
  openRoleCount: 0,
115
+ placedPeopleCount: 5,
114
116
  missingAtNextLevel: null,
115
117
  };
116
118
  expect(() => OrgUnitTreeNodeSchema.parse(base)).not.toThrow();
@@ -118,6 +120,30 @@ describe("OrgUnitTreeNodeSchema", () => {
118
120
  OrgUnitTreeNodeSchema.parse({ ...base, openRoleCount: -1 }),
119
121
  ).toThrow();
120
122
  });
123
+
124
+ it("requires placedPeopleCount, and lets it disagree with memberCount", () => {
125
+ const base = {
126
+ ...makeUnit(),
127
+ depth: 3,
128
+ hasChildren: false,
129
+ // The HRIS shape: nobody in this department has an account, so the
130
+ // account-keyed rollup is 0 while 22 people hold seats in it. Both are
131
+ // correct; they answer different questions (ADR-CONTRACTS-150).
132
+ memberCount: 0,
133
+ openRoleCount: 0,
134
+ placedPeopleCount: 22,
135
+ missingAtNextLevel: null,
136
+ };
137
+ expect(() => OrgUnitTreeNodeSchema.parse(base)).not.toThrow();
138
+
139
+ // Required, not optional — pre-launch there is no back-compat window, and
140
+ // an absent count would be indistinguishable from an empty unit.
141
+ const { placedPeopleCount: _omitted, ...withoutIt } = base;
142
+ expect(() => OrgUnitTreeNodeSchema.parse(withoutIt)).toThrow();
143
+ expect(() =>
144
+ OrgUnitTreeNodeSchema.parse({ ...base, placedPeopleCount: -1 }),
145
+ ).toThrow();
146
+ });
121
147
  });
122
148
 
123
149
  describe("OpenRoleSchema", () => {
@@ -1581,6 +1581,18 @@ export const MissingAtNextLevelSchema = z.object({
1581
1581
  export const OrgUnitTreeNodeSchema = OrgUnitSchema.extend({
1582
1582
  depth: z.number().int().min(1).max(5),
1583
1583
  hasChildren: z.boolean(),
1584
+ /**
1585
+ * ACCOUNTS homed in this unit's subtree — a rollup of `home_assignments`,
1586
+ * whose `user_id` is NOT NULL. A person who has never signed in therefore
1587
+ * cannot contribute to it, and a freshly HRIS-synced org is ~93 people and
1588
+ * one account, so this is legitimately 0 for a fully staffed department.
1589
+ *
1590
+ * That is not a defect: it is the right population for anything that needs an
1591
+ * account to act on — the share dialog's fanout, the unit-scoped members
1592
+ * table. For "how many people are in this department", use
1593
+ * {@link placedPeopleCount}. The two answer different questions and
1594
+ * legitimately disagree (ADR-CONTRACTS-150, ADR-BE-657).
1595
+ */
1584
1596
  memberCount: z.number().int().min(0),
1585
1597
  /**
1586
1598
  * Active open roles (`open` + `hiring`) rolled up over this unit's subtree,
@@ -1588,6 +1600,18 @@ export const OrgUnitTreeNodeSchema = OrgUnitSchema.extend({
1588
1600
  * count split in the people surfaces. `memberCount` stays real-members-only.
1589
1601
  */
1590
1602
  openRoleCount: z.number().int().min(0),
1603
+ /**
1604
+ * Distinct PEOPLE holding a live canonical seat in this unit's subtree —
1605
+ * `placement_role='canonical'`, `status <> 'ended'`, keyed by
1606
+ * `positions.unit_id`. Account linkage is not consulted, so this is the count
1607
+ * that describes an imported workforce.
1608
+ *
1609
+ * Placement and home are independent graphs (ADR-CTRL-159): a person seated
1610
+ * here may be homed elsewhere, and vice versa. Deliberately does NOT filter
1611
+ * `positions.status` — an occupied `closed` seat still draws a card on the
1612
+ * org chart, and a filter here would disagree with it (ADR-BE-657).
1613
+ */
1614
+ placedPeopleCount: z.number().int().min(0),
1591
1615
  missingAtNextLevel: MissingAtNextLevelSchema.nullable(),
1592
1616
  });
1593
1617