@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 +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +117 -0
- package/src/generated/openapi-routes.ts +2 -0
- package/src/index.ts +40 -0
- package/src/org/README.md +24 -0
- package/src/org/__tests__/transformation.test.ts +273 -0
- package/src/org/index.ts +40 -0
- package/src/org/reasoning-review.ts +85 -0
- package/src/org/transformation.ts +215 -0
package/package.json
CHANGED
|
@@ -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 = '409ce57139cd' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = '409ce57139cd78a8ec633efb516bc92ba81dd2cdaab0a4afe240d20c67bb6b1e' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -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>;
|