@company-semantics/contracts 27.1.0 → 27.3.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": "27.1.0",
3
+ "version": "27.3.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 = '0a8d3435dba9' as const;
3
- export const SPEC_HASH_FULL = '0a8d3435dba94642fe2cf529b1743d868363875302393e23cf0b9238a4f5cd47' as const;
2
+ export const SPEC_HASH = '409ce57139cd' as const;
3
+ export const SPEC_HASH_FULL = '409ce57139cd78a8ec633efb516bc92ba81dd2cdaab0a4afe240d20c67bb6b1e' as const;
@@ -1338,6 +1338,40 @@ export interface paths {
1338
1338
  patch?: never;
1339
1339
  trace?: never;
1340
1340
  };
1341
+ "/api/integrations/bamboohr/inferences": {
1342
+ parameters: {
1343
+ query?: never;
1344
+ header?: never;
1345
+ path?: never;
1346
+ cookie?: never;
1347
+ };
1348
+ /** List the org’s pending org-structure reasoning inferences (review queue) */
1349
+ get: operations["listBambooHrInferences"];
1350
+ put?: never;
1351
+ post?: never;
1352
+ delete?: never;
1353
+ options?: never;
1354
+ head?: never;
1355
+ patch?: never;
1356
+ trace?: never;
1357
+ };
1358
+ "/api/integrations/bamboohr/inferences/{id}/decision": {
1359
+ parameters: {
1360
+ query?: never;
1361
+ header?: never;
1362
+ path?: never;
1363
+ cookie?: never;
1364
+ };
1365
+ get?: never;
1366
+ put?: never;
1367
+ /** Accept or reject one pending org-structure reasoning inference */
1368
+ post: operations["decideBambooHrInference"];
1369
+ delete?: never;
1370
+ options?: never;
1371
+ head?: never;
1372
+ patch?: never;
1373
+ trace?: never;
1374
+ };
1341
1375
  "/api/company-md/tree": {
1342
1376
  parameters: {
1343
1377
  query?: never;
@@ -4366,6 +4400,29 @@ export interface components {
4366
4400
  };
4367
4401
  };
4368
4402
  };
4403
+ OrgStructureReviewQueue: {
4404
+ items: {
4405
+ /** Format: uuid */
4406
+ id: string;
4407
+ inferenceType: string;
4408
+ payload: unknown;
4409
+ confidence: number;
4410
+ reason: string;
4411
+ /** Format: date-time */
4412
+ createdAt: string;
4413
+ }[];
4414
+ };
4415
+ OrgStructureInferenceDecisionResult: {
4416
+ /** Format: uuid */
4417
+ id: string;
4418
+ /** @enum {string} */
4419
+ status: "accepted" | "rejected";
4420
+ applied: boolean;
4421
+ };
4422
+ BambooHrInferenceDecision: {
4423
+ /** @enum {string} */
4424
+ decision: "accept" | "reject";
4425
+ };
4369
4426
  CompanyMdListResponse: {
4370
4427
  items: {
4371
4428
  id: string;
@@ -7799,6 +7856,66 @@ export interface operations {
7799
7856
  };
7800
7857
  };
7801
7858
  };
7859
+ listBambooHrInferences: {
7860
+ parameters: {
7861
+ query?: never;
7862
+ header?: never;
7863
+ path?: never;
7864
+ cookie?: never;
7865
+ };
7866
+ requestBody?: never;
7867
+ responses: {
7868
+ /** @description The org’s pending (review) org-structure inferences, newest first */
7869
+ 200: {
7870
+ headers: {
7871
+ [name: string]: unknown;
7872
+ };
7873
+ content: {
7874
+ "application/json": components["schemas"]["OrgStructureReviewQueue"];
7875
+ };
7876
+ };
7877
+ };
7878
+ };
7879
+ decideBambooHrInference: {
7880
+ parameters: {
7881
+ query?: never;
7882
+ header?: never;
7883
+ path: {
7884
+ id: string;
7885
+ };
7886
+ cookie?: never;
7887
+ };
7888
+ requestBody: {
7889
+ content: {
7890
+ "application/json": components["schemas"]["BambooHrInferenceDecision"];
7891
+ };
7892
+ };
7893
+ responses: {
7894
+ /** @description The decision outcome (status + whether the org graph was mutated) */
7895
+ 200: {
7896
+ headers: {
7897
+ [name: string]: unknown;
7898
+ };
7899
+ content: {
7900
+ "application/json": components["schemas"]["OrgStructureInferenceDecisionResult"];
7901
+ };
7902
+ };
7903
+ /** @description Invalid decision value */
7904
+ 400: {
7905
+ headers: {
7906
+ [name: string]: unknown;
7907
+ };
7908
+ content?: never;
7909
+ };
7910
+ /** @description Inference not found or already decided */
7911
+ 404: {
7912
+ headers: {
7913
+ [name: string]: unknown;
7914
+ };
7915
+ content?: never;
7916
+ };
7917
+ };
7918
+ };
7802
7919
  getCompanyMdTree: {
7803
7920
  parameters: {
7804
7921
  query?: {
@@ -62,6 +62,8 @@ export const openApiRoutes = {
62
62
  '/api/factory/kpis': ['GET'],
63
63
  '/api/factory/snapshot': ['GET'],
64
64
  '/api/ingestion/operations/{id}': ['GET'],
65
+ '/api/integrations/bamboohr/inferences': ['GET'],
66
+ '/api/integrations/bamboohr/inferences/{id}/decision': ['POST'],
65
67
  '/api/internal-admin/impersonate/end': ['POST'],
66
68
  '/api/internal-admin/impersonate/session': ['GET'],
67
69
  '/api/internal-admin/impersonate/start': ['POST'],
package/src/index.ts CHANGED
@@ -424,6 +424,46 @@ export type { SyncRunSummary } from "./org/index";
424
424
  export { ConflictRecordSchema } from "./org/index";
425
425
  export type { ConflictRecord } from "./org/index";
426
426
 
427
+ // Org-structure reasoning review queue: pending structure inferences an operator
428
+ // accepts/rejects, and the outcome of one decision. Published response shapes.
429
+ // Provider-agnostic store; BambooHR is the first surface. (ADR-CONT-095)
430
+ export {
431
+ OrgStructureReviewInferenceSchema,
432
+ OrgStructureReviewQueueSchema,
433
+ OrgStructureInferenceDecisionResultSchema,
434
+ } from "./org/index";
435
+ export type {
436
+ OrgStructureReviewInference,
437
+ OrgStructureReviewQueue,
438
+ OrgStructureInferenceDecisionResult,
439
+ } from "./org/index";
440
+
441
+ // Org transformation wire vocabulary: intent -> simulation -> preview. The
442
+ // engine turns a TransformationRequest into scored mutation bundles and returns
443
+ // a TransformationPreview the operator confirms before apply. (ADR-CONTRACTS-072)
444
+ export {
445
+ OrgIntentSchema,
446
+ FactMutationSchema,
447
+ TransformationOriginSchema,
448
+ TransformationRequestSchema,
449
+ ConstraintViolationSchema,
450
+ RecommendationSchema,
451
+ TransformStrategySchema,
452
+ SelectionRationaleSchema,
453
+ TransformationPreviewSchema,
454
+ } from "./org/index";
455
+ export type {
456
+ OrgIntent,
457
+ FactMutation,
458
+ TransformationOrigin,
459
+ TransformationRequest,
460
+ ConstraintViolation,
461
+ Recommendation,
462
+ TransformStrategy,
463
+ SelectionRationale,
464
+ TransformationPreview,
465
+ } from "./org/index";
466
+
427
467
  // Canonical facts: the adapter-seam ingestion batch — external-keyed Canonical*
428
468
  // facts (each with provenance) plus a CanonicalFacts envelope. The reconciler
429
469
  // consumes the batch into the internal org graph. (ADR-CONT-085 / ADR-CTRL-183)
package/src/org/README.md CHANGED
@@ -74,6 +74,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
74
74
  - `CompanyMdVisibility` _(type)_ — Visibility band for a Company.md document — the canonical AUTH `entity_visibility` vocabulary (, backend ). -…
75
75
  - `ConflictRecord` _(type)_
76
76
  - `ConflictRecordSchema`
77
+ - `ConstraintViolation` _(type)_
78
+ - `ConstraintViolationSchema` — One constraint the transformation engine evaluated and found violated.
77
79
  - `CreateDelegationRequest` _(type)_
78
80
  - `CreateDelegationRequestSchema`
79
81
  - `CreateInviteRequest` _(type)_ — Request payload for creating an organization invite.
@@ -95,6 +97,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
95
97
  - `EvaluationStep` _(type)_ — Single step in the access evaluation trace.
96
98
  - `ExecutionScope` _(type)_ — Execution scope determines whose identity is used when executing actions. - 'self': Actions execute under the…
97
99
  - `FACT_SOURCE_TIER_PRECEDENCE` — Truth hierarchy, highest precedence first.
100
+ - `FactMutation` _(type)_
101
+ - `FactMutationSchema` — A single atomic fact change the engine applies to the live org graph.
98
102
  - `FactProvenance` _(type)_
99
103
  - `FactProvenanceSchema` — Provenance envelope attached to a structural fact (reporting edge, org unit, membership, home assignment).
100
104
  - `FactSourceTier` _(type)_
@@ -148,6 +152,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
148
152
  - `OrgDeletionStatus` _(type)_
149
153
  - `OrgDeletionStatusSchema`
150
154
  - `OrgDomain` _(type)_ — Organization domain claim and verification status.
155
+ - `OrgIntent` _(type)_
156
+ - `OrgIntentSchema` — A requested org-graph change expressed as intent.
151
157
  - `OrgInvite` _(type)_ — Organization invite for the workspace invites list.
152
158
  - `OrgInviteStatus` _(type)_ — Status of an organization invite.
153
159
  - `OrgLevelConfig` _(type)_
@@ -159,6 +165,12 @@ Shared type vocabulary for organization ownership, type classification, and tran
159
165
  - `OrgOwnerIcon` _(type)_
160
166
  - `OrgOwnerIconSchema` — Icon options for the `owner` org-chart role.
161
167
  - `OrgScopedContext` _(type)_ — Validated organization-scoped context for policy implementations.
168
+ - `OrgStructureInferenceDecisionResult` _(type)_
169
+ - `OrgStructureInferenceDecisionResultSchema`
170
+ - `OrgStructureReviewInference` _(type)_
171
+ - `OrgStructureReviewInferenceSchema`
172
+ - `OrgStructureReviewQueue` _(type)_
173
+ - `OrgStructureReviewQueueSchema`
162
174
  - `OrgSystemEventsList` _(type)_
163
175
  - `OrgSystemEventsListSchema`
164
176
  - `OrgTreeResponse` _(type)_
@@ -236,6 +248,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
236
248
  - `PositionStatusSchema` — Lifecycle of a seat's EXISTENCE — distinct from a hiring pipeline.
237
249
  - `ProviderStatus` _(type)_ — Provider-level configuration lifecycle.
238
250
  - `ProviderSuggestion` _(type)_ — MX-based provider suggestion for SSO setup.
251
+ - `Recommendation` _(type)_
252
+ - `RecommendationSchema`
239
253
  - `RemoveMemberRequest` _(type)_ — Request payload for removing a member from the workspace.
240
254
  - `RemoveMemberResponse` _(type)_
241
255
  - `RemoveMemberResponseSchema`
@@ -246,6 +260,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
246
260
  - `ScopeCheckBatchResponseSchema`
247
261
  - `ScopeCheckResponse` _(type)_
248
262
  - `ScopeCheckResponseSchema`
263
+ - `SelectionRationale` _(type)_
264
+ - `SelectionRationaleSchema`
249
265
  - `SetActiveOrgResponse` _(type)_
250
266
  - `SetActiveOrgResponseSchema`
251
267
  - `SetMemberManagerResponse` _(type)_
@@ -281,6 +297,14 @@ Shared type vocabulary for organization ownership, type classification, and tran
281
297
  - `TransferMemberEligibility` _(type)_ — Per-member eligibility for transfer recipient picker
282
298
  - `TransferResponsibility` _(type)_
283
299
  - `TransferStatusResponse` _(type)_ — Response for transfer status queries — extends existing OwnershipTransferStatus.
300
+ - `TransformStrategy` _(type)_
301
+ - `TransformStrategySchema`
302
+ - `TransformationOrigin` _(type)_
303
+ - `TransformationOriginSchema` — Where a transformation originated.
304
+ - `TransformationPreview` _(type)_
305
+ - `TransformationPreviewSchema`
306
+ - `TransformationRequest` _(type)_
307
+ - `TransformationRequestSchema`
284
308
  - `TreeOrderableNode` _(type)_ — Canonical per-parent sibling ordering for OrgUnit trees.
285
309
  - `UnitMembershipRole` _(type)_
286
310
  - `UnitMembershipRoleSchema` — Positional slot a member holds in the org-unit tree.
@@ -0,0 +1,273 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ OrgIntentSchema,
4
+ FactMutationSchema,
5
+ TransformationOriginSchema,
6
+ TransformationRequestSchema,
7
+ ConstraintViolationSchema,
8
+ RecommendationSchema,
9
+ TransformStrategySchema,
10
+ SelectionRationaleSchema,
11
+ TransformationPreviewSchema,
12
+ } from "../transformation.js";
13
+
14
+ const REPORT = "33333333-3333-4333-8333-333333333333";
15
+ const MANAGER = "44444444-4444-4444-8444-444444444444";
16
+
17
+ const setManagerIntent = {
18
+ kind: "reporting.setManager" as const,
19
+ reportPositionId: REPORT,
20
+ managerPositionId: MANAGER,
21
+ };
22
+
23
+ const setManagerMutation = {
24
+ kind: "reporting.setManager" as const,
25
+ reportPositionId: REPORT,
26
+ managerPositionId: MANAGER,
27
+ };
28
+
29
+ describe("OrgIntentSchema", () => {
30
+ it("accepts a reporting.setManager intent", () => {
31
+ expect(OrgIntentSchema.parse(setManagerIntent)).toEqual(setManagerIntent);
32
+ });
33
+
34
+ it("rejects an unknown intent kind", () => {
35
+ expect(
36
+ OrgIntentSchema.safeParse({ ...setManagerIntent, kind: "home.setUnit" })
37
+ .success,
38
+ ).toBe(false);
39
+ });
40
+
41
+ it("rejects a non-uuid reportPositionId", () => {
42
+ expect(
43
+ OrgIntentSchema.safeParse({ ...setManagerIntent, reportPositionId: "x" })
44
+ .success,
45
+ ).toBe(false);
46
+ });
47
+
48
+ it("rejects a null managerPositionId (intents always set a manager)", () => {
49
+ expect(
50
+ OrgIntentSchema.safeParse({
51
+ ...setManagerIntent,
52
+ managerPositionId: null,
53
+ }).success,
54
+ ).toBe(false);
55
+ });
56
+ });
57
+
58
+ describe("FactMutationSchema", () => {
59
+ it("accepts a reporting.setManager mutation", () => {
60
+ expect(FactMutationSchema.parse(setManagerMutation)).toEqual(
61
+ setManagerMutation,
62
+ );
63
+ });
64
+
65
+ it("accepts a null managerPositionId (un-setting the manager edge)", () => {
66
+ expect(
67
+ FactMutationSchema.safeParse({
68
+ ...setManagerMutation,
69
+ managerPositionId: null,
70
+ }).success,
71
+ ).toBe(true);
72
+ });
73
+
74
+ it("rejects an unknown mutation kind", () => {
75
+ expect(
76
+ FactMutationSchema.safeParse({ ...setManagerMutation, kind: "unit.move" })
77
+ .success,
78
+ ).toBe(false);
79
+ });
80
+ });
81
+
82
+ describe("TransformationOriginSchema", () => {
83
+ it("accepts each known origin", () => {
84
+ for (const origin of [
85
+ "user_intent",
86
+ "business_event",
87
+ "external_observation",
88
+ ]) {
89
+ expect(TransformationOriginSchema.safeParse(origin).success).toBe(true);
90
+ }
91
+ });
92
+
93
+ it("rejects an unknown origin", () => {
94
+ expect(TransformationOriginSchema.safeParse("system").success).toBe(false);
95
+ });
96
+ });
97
+
98
+ describe("TransformationRequestSchema", () => {
99
+ const request = {
100
+ origin: "user_intent" as const,
101
+ intent: setManagerIntent,
102
+ reason: "New manager after team merge",
103
+ };
104
+
105
+ it("accepts a full request", () => {
106
+ expect(TransformationRequestSchema.parse(request)).toEqual(request);
107
+ });
108
+
109
+ it("accepts a request without a reason", () => {
110
+ const { reason: _omit, ...noReason } = request;
111
+ expect(TransformationRequestSchema.safeParse(noReason).success).toBe(true);
112
+ });
113
+
114
+ it("rejects a reason longer than 2000 chars", () => {
115
+ expect(
116
+ TransformationRequestSchema.safeParse({
117
+ ...request,
118
+ reason: "x".repeat(2001),
119
+ }).success,
120
+ ).toBe(false);
121
+ });
122
+
123
+ it("rejects an unknown origin", () => {
124
+ expect(
125
+ TransformationRequestSchema.safeParse({ ...request, origin: "robot" })
126
+ .success,
127
+ ).toBe(false);
128
+ });
129
+ });
130
+
131
+ describe("ConstraintViolationSchema", () => {
132
+ it("accepts an error violation with a subject", () => {
133
+ expect(
134
+ ConstraintViolationSchema.parse({
135
+ code: "reporting.cycle",
136
+ severity: "error",
137
+ subject: REPORT,
138
+ }),
139
+ ).toEqual({ code: "reporting.cycle", severity: "error", subject: REPORT });
140
+ });
141
+
142
+ it("accepts a warning without a subject", () => {
143
+ expect(
144
+ ConstraintViolationSchema.safeParse({
145
+ code: "span.wide",
146
+ severity: "warning",
147
+ }).success,
148
+ ).toBe(true);
149
+ });
150
+
151
+ it("rejects an unknown severity", () => {
152
+ expect(
153
+ ConstraintViolationSchema.safeParse({ code: "c", severity: "fatal" })
154
+ .success,
155
+ ).toBe(false);
156
+ });
157
+ });
158
+
159
+ describe("RecommendationSchema / TransformStrategySchema", () => {
160
+ const recommendation = {
161
+ label: "Reassign report",
162
+ mutations: [setManagerMutation],
163
+ cost: 1,
164
+ explanation: ["Direct edge rewrite", "No descendants affected"],
165
+ };
166
+
167
+ it("accepts a recommendation without a warning", () => {
168
+ expect(RecommendationSchema.parse(recommendation)).toEqual(recommendation);
169
+ });
170
+
171
+ it("accepts a recommendation with a warning", () => {
172
+ expect(
173
+ RecommendationSchema.safeParse({
174
+ ...recommendation,
175
+ warning: "Crosses a department boundary",
176
+ }).success,
177
+ ).toBe(true);
178
+ });
179
+
180
+ it("accepts a strategy wrapping recommendations", () => {
181
+ expect(
182
+ TransformStrategySchema.parse({
183
+ recommendations: [recommendation],
184
+ totalCost: 1,
185
+ warnings: [],
186
+ }).recommendations,
187
+ ).toHaveLength(1);
188
+ });
189
+
190
+ it("rejects a recommendation with a non-array explanation", () => {
191
+ expect(
192
+ RecommendationSchema.safeParse({ ...recommendation, explanation: "one" })
193
+ .success,
194
+ ).toBe(false);
195
+ });
196
+ });
197
+
198
+ describe("SelectionRationaleSchema", () => {
199
+ it("accepts chosen reasons and rejected alternatives", () => {
200
+ const rationale = {
201
+ chosen: ["Lowest cost"],
202
+ rejectedAlternatives: [
203
+ {
204
+ label: "Restructure subtree",
205
+ reasons: ["Higher cost", "Wider span"],
206
+ },
207
+ ],
208
+ };
209
+ expect(SelectionRationaleSchema.parse(rationale)).toEqual(rationale);
210
+ });
211
+
212
+ it("accepts an empty rejected-alternatives list", () => {
213
+ expect(
214
+ SelectionRationaleSchema.safeParse({
215
+ chosen: ["Only option"],
216
+ rejectedAlternatives: [],
217
+ }).success,
218
+ ).toBe(true);
219
+ });
220
+ });
221
+
222
+ describe("TransformationPreviewSchema", () => {
223
+ const strategy = {
224
+ recommendations: [
225
+ {
226
+ label: "Reassign report",
227
+ mutations: [setManagerMutation],
228
+ cost: 1,
229
+ explanation: ["Direct edge rewrite"],
230
+ },
231
+ ],
232
+ totalCost: 1,
233
+ warnings: [],
234
+ };
235
+
236
+ const preview = {
237
+ intentLabel: "Set manager",
238
+ primary: setManagerMutation,
239
+ defaultStrategy: strategy,
240
+ alternatives: [],
241
+ warnings: [],
242
+ selectionRationale: { chosen: ["Lowest cost"], rejectedAlternatives: [] },
243
+ };
244
+
245
+ it("accepts a preview without a hardBlock", () => {
246
+ expect(TransformationPreviewSchema.parse(preview)).toEqual(preview);
247
+ });
248
+
249
+ it("accepts a preview with a hardBlock", () => {
250
+ expect(
251
+ TransformationPreviewSchema.safeParse({
252
+ ...preview,
253
+ hardBlock: { code: "reporting.cycle", reason: "Would create a cycle" },
254
+ }).success,
255
+ ).toBe(true);
256
+ });
257
+
258
+ it("rejects a hardBlock missing its reason", () => {
259
+ expect(
260
+ TransformationPreviewSchema.safeParse({
261
+ ...preview,
262
+ hardBlock: { code: "reporting.cycle" },
263
+ }).success,
264
+ ).toBe(false);
265
+ });
266
+
267
+ it("rejects a preview missing the primary mutation", () => {
268
+ const { primary: _omit, ...noPrimary } = preview;
269
+ expect(TransformationPreviewSchema.safeParse(noPrimary).success).toBe(
270
+ false,
271
+ );
272
+ });
273
+ });
package/src/org/index.ts CHANGED
@@ -136,6 +136,46 @@ export type { SyncRunSummary } from "./sync-run";
136
136
  export { ConflictRecordSchema } from "./conflict-record";
137
137
  export type { ConflictRecord } from "./conflict-record";
138
138
 
139
+ // Org-structure reasoning review queue: pending structure inferences an operator
140
+ // accepts/rejects, and the outcome of one decision. Published response shapes;
141
+ // provider-agnostic store, BambooHR is the first surface. (ADR-CONT-095)
142
+ export {
143
+ OrgStructureReviewInferenceSchema,
144
+ OrgStructureReviewQueueSchema,
145
+ OrgStructureInferenceDecisionResultSchema,
146
+ } from "./reasoning-review";
147
+ export type {
148
+ OrgStructureReviewInference,
149
+ OrgStructureReviewQueue,
150
+ OrgStructureInferenceDecisionResult,
151
+ } from "./reasoning-review";
152
+
153
+ // Org transformation wire vocabulary: intent -> simulation -> preview. The
154
+ // engine turns a TransformationRequest into scored mutation bundles and returns
155
+ // a TransformationPreview the operator confirms before apply. (ADR-CONTRACTS-072)
156
+ export {
157
+ OrgIntentSchema,
158
+ FactMutationSchema,
159
+ TransformationOriginSchema,
160
+ TransformationRequestSchema,
161
+ ConstraintViolationSchema,
162
+ RecommendationSchema,
163
+ TransformStrategySchema,
164
+ SelectionRationaleSchema,
165
+ TransformationPreviewSchema,
166
+ } from "./transformation";
167
+ export type {
168
+ OrgIntent,
169
+ FactMutation,
170
+ TransformationOrigin,
171
+ TransformationRequest,
172
+ ConstraintViolation,
173
+ Recommendation,
174
+ TransformStrategy,
175
+ SelectionRationale,
176
+ TransformationPreview,
177
+ } from "./transformation";
178
+
139
179
  // Canonical facts: the adapter-seam ingestion batch. Each fact is keyed by
140
180
  // external identity (externalSourceId/externalSourceSystem) and carries a
141
181
  // FactProvenance envelope; the reconciler consumes the batch. (ADR-CONT-085 / ADR-CTRL-183)
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Org-structure reasoning review vocabulary — the operator review queue for
3
+ * proposed structure changes and the outcome of deciding one.
4
+ *
5
+ * An org-structure reasoning pass (the adapter → reasoning-engine pipeline,
6
+ * BambooHR being the guinea-pig source — control ADR-CTRL-159, backend
7
+ * ADR-BE-345) emits structure INFERENCES (nest a unit, place a report, ...) that
8
+ * are held in `review` rather than applied blindly. The review queue is the set
9
+ * of an org's pending inferences; an operator with integration-management
10
+ * authority ACCEPTS one (re-applying it to the live org graph) or REJECTS it.
11
+ *
12
+ * These are published RESPONSE shapes (ADR-CONT-029: response/published shapes
13
+ * live in contracts; the `decision` request body stays backend-local). They are
14
+ * provider-agnostic — the underlying store (`org_structure_inferences`) is not
15
+ * BambooHR-specific even though the first surface that reads it is the BambooHR
16
+ * integration route. `inferenceType` is an OPEN string (no enum) so new
17
+ * inference kinds need no schema change, mirroring the sibling org schemas
18
+ * ({@link ConflictRecordSchema}, {@link SyncRunSummarySchema}). `payload` is the
19
+ * inference-kind-specific body, modeled as unknown — its shape is keyed off
20
+ * `inferenceType` and is not part of the stable contract surface.
21
+ */
22
+ import { z } from "zod";
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // OrgStructureReviewInference — one pending ('review') structure inference
26
+ // ---------------------------------------------------------------------------
27
+
28
+ export const OrgStructureReviewInferenceSchema = z.object({
29
+ /** Identifier of this inference within the org graph. */
30
+ id: z.string().uuid(),
31
+ /**
32
+ * Kind of structure inference (e.g. "NestOrgUnit", "PlaceReport"). An OPEN
33
+ * string so new inference kinds need no schema change.
34
+ */
35
+ inferenceType: z.string().min(1),
36
+ /**
37
+ * The inference-kind-specific body. Its shape is keyed off `inferenceType`
38
+ * and is intentionally unmodeled here — not part of the stable contract.
39
+ */
40
+ payload: z.unknown(),
41
+ /** Engine confidence in this inference, 0..1. */
42
+ confidence: z.number(),
43
+ /** Human-meaningful WHY the engine proposed this change. */
44
+ reason: z.string(),
45
+ /** ISO timestamp the inference was created. */
46
+ createdAt: z.string().datetime(),
47
+ });
48
+
49
+ export type OrgStructureReviewInference = z.infer<
50
+ typeof OrgStructureReviewInferenceSchema
51
+ >;
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // OrgStructureReviewQueue — an org's pending review inferences (newest first)
55
+ // ---------------------------------------------------------------------------
56
+
57
+ export const OrgStructureReviewQueueSchema = z.object({
58
+ /** The org's pending ('review') inferences, newest first. */
59
+ items: z.array(OrgStructureReviewInferenceSchema),
60
+ });
61
+
62
+ export type OrgStructureReviewQueue = z.infer<
63
+ typeof OrgStructureReviewQueueSchema
64
+ >;
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // OrgStructureInferenceDecisionResult — outcome of accepting/rejecting one
68
+ // ---------------------------------------------------------------------------
69
+
70
+ export const OrgStructureInferenceDecisionResultSchema = z.object({
71
+ /** Identifier of the decided inference. */
72
+ id: z.string().uuid(),
73
+ /** The terminal status the inference was moved to. */
74
+ status: z.enum(["accepted", "rejected"]),
75
+ /**
76
+ * Whether the decision mutated the live org graph. True on an accept that
77
+ * re-applied the inference; false on a reject (and on an accept that resolved
78
+ * to a no-op).
79
+ */
80
+ applied: z.boolean(),
81
+ });
82
+
83
+ export type OrgStructureInferenceDecisionResult = z.infer<
84
+ typeof OrgStructureInferenceDecisionResultSchema
85
+ >;
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Org transformation wire vocabulary — intent → simulation → preview.
3
+ *
4
+ * A transformation is a deliberate change to the org graph expressed at the
5
+ * level of INTENT ("make this seat report to that one") rather than raw fact
6
+ * writes. The semantic transformation engine (control ADR-CTRL — see
7
+ * ADR-CONTRACTS-072) turns one {@link TransformationRequestSchema} into a set of
8
+ * candidate {@link FactMutationSchema} bundles, scores them deterministically,
9
+ * and returns a {@link TransformationPreviewSchema} the operator confirms before
10
+ * anything is applied. This module is the shared WIRE vocabulary for that flow.
11
+ *
12
+ * Layers, smallest → largest:
13
+ * - {@link OrgIntentSchema} — WHAT the operator wants, as a discriminated union
14
+ * on `kind`. Today only `reporting.setManager`; shaped so future kinds
15
+ * (home.setUnit, unit.move, position.open, position.close) append without
16
+ * breaking existing consumers.
17
+ * - {@link FactMutationSchema} — the typed ATOMIC fact change the engine applies
18
+ * to the live graph. Distinct from intent: an intent may expand into several
19
+ * mutations, and a mutation can express un-setting (a null target) that an
20
+ * intent never does.
21
+ * - {@link TransformationRequestSchema} — an intent plus its `origin` and an
22
+ * optional human WHY (`reason`).
23
+ * - {@link RecommendationSchema} / {@link TransformStrategySchema} — the scored,
24
+ * explained candidate bundles the engine produces.
25
+ * - {@link TransformationPreviewSchema} — the full operator-facing preview: the
26
+ * primary mutation, the default strategy, alternatives, and the deterministic
27
+ * rationale for what was chosen.
28
+ *
29
+ * NOTE ON PLACEMENT: these are PUBLISHED response/preview shapes (ADR-CONT-029:
30
+ * response shapes live in contracts, request bodies stay backend-local). The
31
+ * transformation REQUEST body the backend route accepts is modeled there; this
32
+ * module carries only the vocabulary the contract surface promises.
33
+ *
34
+ * Reporting edge field names (`reportPositionId` / `managerPositionId`) match
35
+ * {@link PositionReportingSchema} exactly so intent, mutation, and the canonical
36
+ * edge speak one language.
37
+ *
38
+ * @see ADR-CONTRACTS-072 for the transformation vocabulary decision
39
+ * @see position-reporting.ts for the canonical reporting-edge field names
40
+ * @see structure-facts.ts (FactProvenance.reason) for the human-WHY precedent
41
+ */
42
+ import { z } from "zod";
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // OrgIntent — WHAT the operator wants (discriminated union, designed to grow)
46
+ // ---------------------------------------------------------------------------
47
+
48
+ /**
49
+ * A requested org-graph change expressed as intent. Discriminated on `kind`, a
50
+ * dotted string naming the affected relation and verb. Today the only variant
51
+ * is `reporting.setManager` — point a reporting seat at a (non-null) manager
52
+ * seat. Future kinds (`home.setUnit`, `unit.move`, `position.open`,
53
+ * `position.close`) append as new union members without breaking consumers.
54
+ */
55
+ export const OrgIntentSchema = z.discriminatedUnion("kind", [
56
+ z.object({
57
+ kind: z.literal("reporting.setManager"),
58
+ /** The reporting (subordinate) seat whose manager is being set. */
59
+ reportPositionId: z.string().uuid(),
60
+ /** The manager seat to report to. Non-null on an intent (un-setting is a mutation concern). */
61
+ managerPositionId: z.string().uuid(),
62
+ }),
63
+ ]);
64
+ export type OrgIntent = z.infer<typeof OrgIntentSchema>;
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // FactMutation — the atomic fact change the engine applies
68
+ // ---------------------------------------------------------------------------
69
+
70
+ /**
71
+ * A single atomic fact change the engine applies to the live org graph.
72
+ * Discriminated on `kind`, mirroring {@link OrgIntentSchema}. Unlike an intent,
73
+ * a mutation can express UN-SETTING: `reporting.setManager` with a null
74
+ * `managerPositionId` clears the seat's manager edge entirely.
75
+ */
76
+ export const FactMutationSchema = z.discriminatedUnion("kind", [
77
+ z.object({
78
+ kind: z.literal("reporting.setManager"),
79
+ /** The reporting (subordinate) seat whose manager edge is being written. */
80
+ reportPositionId: z.string().uuid(),
81
+ /** The new manager seat, or `null` to un-set (clear) the manager edge. */
82
+ managerPositionId: z.string().uuid().nullable(),
83
+ }),
84
+ ]);
85
+ export type FactMutation = z.infer<typeof FactMutationSchema>;
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // TransformationRequest — an intent plus origin and an optional human WHY
89
+ // ---------------------------------------------------------------------------
90
+
91
+ /**
92
+ * Where a transformation originated. Closed set: a direct `user_intent`, a
93
+ * `business_event` (e.g. a sync-observed reorg), or an `external_observation`
94
+ * (an inference proposed from outside the system).
95
+ */
96
+ export const TransformationOriginSchema = z.enum([
97
+ "user_intent",
98
+ "business_event",
99
+ "external_observation",
100
+ ]);
101
+ export type TransformationOrigin = z.infer<typeof TransformationOriginSchema>;
102
+
103
+ export const TransformationRequestSchema = z.object({
104
+ /** Provenance class of the request — drives how much scrutiny it gets. */
105
+ origin: TransformationOriginSchema,
106
+ /** The requested change, as intent. */
107
+ intent: OrgIntentSchema,
108
+ /**
109
+ * Human-meaningful WHY for an authority-relevant change — a free-text audit
110
+ * record of intent, capped at 2000 chars to mirror {@link FactProvenanceSchema}'s
111
+ * `reason`. `null`/absent for machine-originated requests. It records why a
112
+ * change was requested, NOT who may make it (ADR-CTRL-180).
113
+ */
114
+ reason: z.string().max(2000).nullable().optional(),
115
+ });
116
+ export type TransformationRequest = z.infer<typeof TransformationRequestSchema>;
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // ConstraintViolation — a rule the engine found broken or at risk
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /**
123
+ * One constraint the transformation engine evaluated and found violated. `code`
124
+ * is an open string naming the rule; `severity` distinguishes a hard `error`
125
+ * (the change is rejected) from a `warning` (allowed but flagged); `subject` is
126
+ * the optional id of the entity the violation is about.
127
+ */
128
+ export const ConstraintViolationSchema = z.object({
129
+ /** Open string naming the violated rule (e.g. "reporting.cycle"). */
130
+ code: z.string().min(1),
131
+ /** `error` blocks the change; `warning` allows it but surfaces a caution. */
132
+ severity: z.enum(["error", "warning"]),
133
+ /** Optional id of the entity the violation concerns. */
134
+ subject: z.string().optional(),
135
+ });
136
+ export type ConstraintViolation = z.infer<typeof ConstraintViolationSchema>;
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Recommendation — one scored, explained candidate bundle of mutations
140
+ // ---------------------------------------------------------------------------
141
+
142
+ export const RecommendationSchema = z.object({
143
+ /** Short human label for this candidate (e.g. "Move report to new manager"). */
144
+ label: z.string().min(1),
145
+ /** The atomic fact changes this candidate would apply. */
146
+ mutations: z.array(FactMutationSchema),
147
+ /** Deterministic relative cost of this candidate; lower is preferred. */
148
+ cost: z.number(),
149
+ /** Optional single caution shown alongside this candidate. */
150
+ warning: z.string().optional(),
151
+ /** Deterministic, ordered reasons explaining how this candidate was derived. */
152
+ explanation: z.array(z.string()),
153
+ });
154
+ export type Recommendation = z.infer<typeof RecommendationSchema>;
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // TransformStrategy — a scored set of recommendations
158
+ // ---------------------------------------------------------------------------
159
+
160
+ export const TransformStrategySchema = z.object({
161
+ /** Candidate bundles, in the engine's preference order. */
162
+ recommendations: z.array(RecommendationSchema),
163
+ /** Aggregate cost across the strategy's recommendations. */
164
+ totalCost: z.number(),
165
+ /** Strategy-level cautions not tied to a single recommendation. */
166
+ warnings: z.array(z.string()),
167
+ });
168
+ export type TransformStrategy = z.infer<typeof TransformStrategySchema>;
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // SelectionRationale — why the default strategy was chosen over alternatives
172
+ // ---------------------------------------------------------------------------
173
+
174
+ export const SelectionRationaleSchema = z.object({
175
+ /** Ordered reasons the chosen strategy won. */
176
+ chosen: z.array(z.string()),
177
+ /** Each alternative that lost, with the ordered reasons it was rejected. */
178
+ rejectedAlternatives: z.array(
179
+ z.object({
180
+ label: z.string().min(1),
181
+ reasons: z.array(z.string()),
182
+ }),
183
+ ),
184
+ });
185
+ export type SelectionRationale = z.infer<typeof SelectionRationaleSchema>;
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // TransformationPreview — the full operator-facing preview before apply
189
+ // ---------------------------------------------------------------------------
190
+
191
+ export const TransformationPreviewSchema = z.object({
192
+ /** Human label for the intent being previewed. */
193
+ intentLabel: z.string().min(1),
194
+ /** The headline fact change the preview is centered on. */
195
+ primary: FactMutationSchema,
196
+ /** The strategy the engine recommends by default. */
197
+ defaultStrategy: TransformStrategySchema,
198
+ /** Other viable strategies the operator may pick instead. */
199
+ alternatives: z.array(TransformStrategySchema),
200
+ /** Preview-level cautions spanning the whole transformation. */
201
+ warnings: z.array(z.string()),
202
+ /** Deterministic rationale for the default choice over the alternatives. */
203
+ selectionRationale: SelectionRationaleSchema,
204
+ /**
205
+ * Present only when the transformation is hard-blocked: a terminal reason the
206
+ * change cannot proceed at all. Absent on a previewable (confirmable) change.
207
+ */
208
+ hardBlock: z
209
+ .object({
210
+ code: z.string().min(1),
211
+ reason: z.string().min(1),
212
+ })
213
+ .optional(),
214
+ });
215
+ export type TransformationPreview = z.infer<typeof TransformationPreviewSchema>;