@company-semantics/contracts 89.0.0 → 89.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 +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +956 -1
- package/src/generated/openapi-routes.ts +15 -0
- package/src/message-parts/README.md +3 -0
- package/src/message-parts/__tests__/workflow-outcome.test.ts +41 -0
- package/src/message-parts/index.ts +7 -0
- package/src/message-parts/types.ts +7 -1
- package/src/message-parts/wire.ts +26 -0
- package/src/message-parts/workflow-outcome.ts +54 -0
- package/src/root-exports/agent-runtime.ts +4 -0
- package/src/root-exports/delivery.ts +36 -0
- package/src/workflows/README.md +17 -1
- package/src/workflows/__tests__/README.md +6 -0
- package/src/workflows/__tests__/outcome.test.ts +130 -0
- package/src/workflows/index.ts +33 -0
- package/src/workflows/outcome.ts +299 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed outcome of an analysis workflow's terminal transition
|
|
3
|
+
* (control ADR-CTRL-540, the Workflow Outcome Settlement Invariant).
|
|
4
|
+
*
|
|
5
|
+
* The workflow lifecycle says THAT a run ended (`stopped | complete | failed`);
|
|
6
|
+
* this says WHAT KIND of ending it was, in a vocabulary a renderer, a narrator
|
|
7
|
+
* and the model can all switch on exhaustively. The backend classifies it ONCE,
|
|
8
|
+
* atomically with the terminal transition, and persists it immutably; delivery
|
|
9
|
+
* and every reader consume the frozen value and never re-decide it.
|
|
10
|
+
*
|
|
11
|
+
* Zod-canonical: the schemas are the source of truth, the types are inferred.
|
|
12
|
+
*
|
|
13
|
+
* INVARIANTS:
|
|
14
|
+
* - PER-TYPE AND CLOSED. Every workflow type has its own closed `reason` union
|
|
15
|
+
* and its own typed `facts`. There is no free-form reason and no open facts
|
|
16
|
+
* record: a renderer that switches on `reason` is exhaustive, and a new fact is
|
|
17
|
+
* a schema change, not a new spelling.
|
|
18
|
+
* - KIND IS A FUNCTION OF REASON. `kind` is carried for consumers that only care
|
|
19
|
+
* about the shape of the ending, but it is fixed by the per-type table below
|
|
20
|
+
* (`*_OUTCOME_KIND_OF_REASON`) and the schema refuses any other pairing.
|
|
21
|
+
* - DISPOSITION IS EXPLICIT. `review` names the input request a person decides
|
|
22
|
+
* on; `narrate` means the ending is told, not reviewed. `produced` does not
|
|
23
|
+
* imply a review surface, and only a `produced` or `partial` outcome may carry
|
|
24
|
+
* `review`.
|
|
25
|
+
* - `unknown` IS A SAFETY VALVE for a terminal transition the classifier did not
|
|
26
|
+
* recognize. It is never used for a failure to OBSERVE a workflow: a workflow
|
|
27
|
+
* whose state cannot be read has no outcome yet.
|
|
28
|
+
* - FACTS ARE COUNTS. Counts and closed codes only, never names, titles, paths
|
|
29
|
+
* or content: a settled outcome is persisted into chat parts that token-only
|
|
30
|
+
* share readers receive verbatim. `facts` is null when the execution reported
|
|
31
|
+
* none (a reconciler abandon, a refusal before the run began).
|
|
32
|
+
*/
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
|
|
35
|
+
import { INPUT_REQUEST_TYPES } from "../input-requests/types";
|
|
36
|
+
|
|
37
|
+
/** The shape of an ending, shared by every workflow type. */
|
|
38
|
+
export const WORKFLOW_OUTCOME_KINDS = [
|
|
39
|
+
/** The run produced what it is for. */
|
|
40
|
+
"produced",
|
|
41
|
+
/** The run finished and produced nothing. */
|
|
42
|
+
"empty",
|
|
43
|
+
/** The run finished short of full coverage (a budget or ceiling). */
|
|
44
|
+
"partial",
|
|
45
|
+
/** A person stopped the run; Continue may begin a new execution epoch. */
|
|
46
|
+
"stopped",
|
|
47
|
+
/** The run failed. */
|
|
48
|
+
"failed",
|
|
49
|
+
/** The run went silent and the reconciler closed it. */
|
|
50
|
+
"abandoned",
|
|
51
|
+
/** A terminal transition the classifier did not recognize. */
|
|
52
|
+
"unknown",
|
|
53
|
+
] as const;
|
|
54
|
+
|
|
55
|
+
export type WorkflowOutcomeKind = (typeof WORKFLOW_OUTCOME_KINDS)[number];
|
|
56
|
+
|
|
57
|
+
const KindSchema = z.enum(WORKFLOW_OUTCOME_KINDS);
|
|
58
|
+
|
|
59
|
+
/** What delivering the outcome does. */
|
|
60
|
+
export const WorkflowOutcomeDispositionSchema = z.discriminatedUnion("kind", [
|
|
61
|
+
z
|
|
62
|
+
.object({
|
|
63
|
+
kind: z.literal("review"),
|
|
64
|
+
requestType: z.enum(INPUT_REQUEST_TYPES),
|
|
65
|
+
requestId: z.string().min(1),
|
|
66
|
+
})
|
|
67
|
+
.strict(),
|
|
68
|
+
z.object({ kind: z.literal("narrate") }).strict(),
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
export type WorkflowOutcomeDisposition = z.infer<
|
|
72
|
+
typeof WorkflowOutcomeDispositionSchema
|
|
73
|
+
>;
|
|
74
|
+
|
|
75
|
+
const Count = z.number().int().nonnegative();
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// source-sync/discover
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
/** Why a Drive discovery run ended the way it did. */
|
|
82
|
+
export const DISCOVER_OUTCOME_KIND_OF_REASON = {
|
|
83
|
+
candidates_found: "produced",
|
|
84
|
+
nothing_found: "empty",
|
|
85
|
+
nothing_relevant: "empty",
|
|
86
|
+
budget_exhausted: "partial",
|
|
87
|
+
file_ceiling: "partial",
|
|
88
|
+
stopped_by_user: "stopped",
|
|
89
|
+
plan_coverage_refused: "failed",
|
|
90
|
+
billing_refused: "failed",
|
|
91
|
+
drive_auth: "failed",
|
|
92
|
+
drive_unavailable: "failed",
|
|
93
|
+
no_connection: "failed",
|
|
94
|
+
source_sync_off: "failed",
|
|
95
|
+
internal_error: "failed",
|
|
96
|
+
abandoned: "abandoned",
|
|
97
|
+
unrecognized_terminal_state: "unknown",
|
|
98
|
+
} as const satisfies Record<string, WorkflowOutcomeKind>;
|
|
99
|
+
|
|
100
|
+
export type DiscoverOutcomeReason =
|
|
101
|
+
keyof typeof DISCOVER_OUTCOME_KIND_OF_REASON;
|
|
102
|
+
|
|
103
|
+
/** The counts a discovery run reports at its end. */
|
|
104
|
+
export const DiscoverOutcomeFactsSchema = z
|
|
105
|
+
.object({
|
|
106
|
+
probesTotal: Count,
|
|
107
|
+
probesDone: Count,
|
|
108
|
+
filesSeen: Count,
|
|
109
|
+
filesShown: Count,
|
|
110
|
+
filesHidden: Count,
|
|
111
|
+
filesDropped: Count,
|
|
112
|
+
coveragePercent: z.number().min(0).max(100).nullable(),
|
|
113
|
+
})
|
|
114
|
+
.strict();
|
|
115
|
+
|
|
116
|
+
export type DiscoverOutcomeFacts = z.infer<typeof DiscoverOutcomeFactsSchema>;
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// source-sync/synthesize
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
/** Why a synthesis (landing then drafting) run ended the way it did. */
|
|
123
|
+
export const SYNTHESIZE_OUTCOME_KIND_OF_REASON = {
|
|
124
|
+
updates_ready: "produced",
|
|
125
|
+
updates_applied: "produced",
|
|
126
|
+
stopped_by_user: "stopped",
|
|
127
|
+
paused_billing: "failed",
|
|
128
|
+
synthesis_incomplete: "failed",
|
|
129
|
+
approval_mismatch: "failed",
|
|
130
|
+
landing_refused: "failed",
|
|
131
|
+
standing_order_refused: "failed",
|
|
132
|
+
no_connection: "failed",
|
|
133
|
+
source_sync_off: "failed",
|
|
134
|
+
internal_error: "failed",
|
|
135
|
+
abandoned: "abandoned",
|
|
136
|
+
unrecognized_terminal_state: "unknown",
|
|
137
|
+
} as const satisfies Record<string, WorkflowOutcomeKind>;
|
|
138
|
+
|
|
139
|
+
export type SynthesizeOutcomeReason =
|
|
140
|
+
keyof typeof SYNTHESIZE_OUTCOME_KIND_OF_REASON;
|
|
141
|
+
|
|
142
|
+
/** The counts a synthesis run reports at its end. */
|
|
143
|
+
export const SynthesizeOutcomeFactsSchema = z
|
|
144
|
+
.object({
|
|
145
|
+
/** Approved scopes the run read. */
|
|
146
|
+
scopeCount: Count,
|
|
147
|
+
/** Authority or size violations that left the batch incomplete. */
|
|
148
|
+
violationCount: Count,
|
|
149
|
+
})
|
|
150
|
+
.strict();
|
|
151
|
+
|
|
152
|
+
export type SynthesizeOutcomeFacts = z.infer<
|
|
153
|
+
typeof SynthesizeOutcomeFactsSchema
|
|
154
|
+
>;
|
|
155
|
+
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// org-structure/hris-connect and org-structure/reanalyze
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Why an org-structure analysis ended the way it did. Shared by both
|
|
162
|
+
* org-structure workflow types: they run the same analysis. The directory-only
|
|
163
|
+
* endings (`directory_synced`, `analysis_already_running`, `sync_failed`) occur
|
|
164
|
+
* only on `org-structure/hris-connect`, which syncs the directory first.
|
|
165
|
+
*/
|
|
166
|
+
export const ORG_STRUCTURE_OUTCOME_KIND_OF_REASON = {
|
|
167
|
+
proposal_ready: "produced",
|
|
168
|
+
directory_synced: "produced",
|
|
169
|
+
analysis_already_running: "produced",
|
|
170
|
+
proposal_blocked: "failed",
|
|
171
|
+
no_sync_run: "empty",
|
|
172
|
+
no_persons: "empty",
|
|
173
|
+
reporting_not_keyed: "empty",
|
|
174
|
+
stopped_by_user: "stopped",
|
|
175
|
+
sync_failed: "failed",
|
|
176
|
+
snapshot_too_large: "failed",
|
|
177
|
+
model_failed: "failed",
|
|
178
|
+
run_too_large: "failed",
|
|
179
|
+
placement_failed: "failed",
|
|
180
|
+
internal_error: "failed",
|
|
181
|
+
abandoned: "abandoned",
|
|
182
|
+
unrecognized_terminal_state: "unknown",
|
|
183
|
+
} as const satisfies Record<string, WorkflowOutcomeKind>;
|
|
184
|
+
|
|
185
|
+
export type OrgStructureOutcomeReason =
|
|
186
|
+
keyof typeof ORG_STRUCTURE_OUTCOME_KIND_OF_REASON;
|
|
187
|
+
|
|
188
|
+
/** The counts a finished org-structure proposal carries. */
|
|
189
|
+
export const OrgStructureOutcomeFactsSchema = z
|
|
190
|
+
.object({
|
|
191
|
+
departmentCount: Count,
|
|
192
|
+
teamCount: Count,
|
|
193
|
+
placedCount: Count,
|
|
194
|
+
reviewItemCount: Count,
|
|
195
|
+
})
|
|
196
|
+
.strict();
|
|
197
|
+
|
|
198
|
+
export type OrgStructureOutcomeFacts = z.infer<
|
|
199
|
+
typeof OrgStructureOutcomeFactsSchema
|
|
200
|
+
>;
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// The union
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
function memberSchema<
|
|
207
|
+
T extends string,
|
|
208
|
+
R extends Record<string, WorkflowOutcomeKind>,
|
|
209
|
+
F extends z.ZodTypeAny,
|
|
210
|
+
>(workflowType: T, kindOfReason: R, facts: F) {
|
|
211
|
+
const reasons = Object.keys(kindOfReason) as [
|
|
212
|
+
keyof R & string,
|
|
213
|
+
...(keyof R & string)[],
|
|
214
|
+
];
|
|
215
|
+
return z
|
|
216
|
+
.object({
|
|
217
|
+
workflowType: z.literal(workflowType),
|
|
218
|
+
/** The execution epoch this ending closes (Continue begins the next). */
|
|
219
|
+
executionEpoch: Count,
|
|
220
|
+
kind: KindSchema,
|
|
221
|
+
reason: z.enum(reasons),
|
|
222
|
+
facts: facts.nullable(),
|
|
223
|
+
disposition: WorkflowOutcomeDispositionSchema,
|
|
224
|
+
})
|
|
225
|
+
.strict()
|
|
226
|
+
.superRefine((outcome, ctx) => {
|
|
227
|
+
const expected = kindOfReason[outcome.reason as keyof R];
|
|
228
|
+
if (outcome.kind !== expected) {
|
|
229
|
+
ctx.addIssue({
|
|
230
|
+
code: z.ZodIssueCode.custom,
|
|
231
|
+
path: ["kind"],
|
|
232
|
+
message: `reason '${String(outcome.reason)}' has kind '${expected}'`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (
|
|
236
|
+
outcome.disposition.kind === "review" &&
|
|
237
|
+
outcome.kind !== "produced" &&
|
|
238
|
+
outcome.kind !== "partial"
|
|
239
|
+
) {
|
|
240
|
+
ctx.addIssue({
|
|
241
|
+
code: z.ZodIssueCode.custom,
|
|
242
|
+
path: ["disposition"],
|
|
243
|
+
message: "only a produced or partial outcome may hand off to review",
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export const DiscoverWorkflowOutcomeSchema = memberSchema(
|
|
250
|
+
"source-sync/discover",
|
|
251
|
+
DISCOVER_OUTCOME_KIND_OF_REASON,
|
|
252
|
+
DiscoverOutcomeFactsSchema,
|
|
253
|
+
);
|
|
254
|
+
export const SynthesizeWorkflowOutcomeSchema = memberSchema(
|
|
255
|
+
"source-sync/synthesize",
|
|
256
|
+
SYNTHESIZE_OUTCOME_KIND_OF_REASON,
|
|
257
|
+
SynthesizeOutcomeFactsSchema,
|
|
258
|
+
);
|
|
259
|
+
export const HrisConnectWorkflowOutcomeSchema = memberSchema(
|
|
260
|
+
"org-structure/hris-connect",
|
|
261
|
+
ORG_STRUCTURE_OUTCOME_KIND_OF_REASON,
|
|
262
|
+
OrgStructureOutcomeFactsSchema,
|
|
263
|
+
);
|
|
264
|
+
export const ReanalyzeWorkflowOutcomeSchema = memberSchema(
|
|
265
|
+
"org-structure/reanalyze",
|
|
266
|
+
ORG_STRUCTURE_OUTCOME_KIND_OF_REASON,
|
|
267
|
+
OrgStructureOutcomeFactsSchema,
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
export type DiscoverWorkflowOutcome = z.infer<
|
|
271
|
+
typeof DiscoverWorkflowOutcomeSchema
|
|
272
|
+
>;
|
|
273
|
+
export type SynthesizeWorkflowOutcome = z.infer<
|
|
274
|
+
typeof SynthesizeWorkflowOutcomeSchema
|
|
275
|
+
>;
|
|
276
|
+
export type HrisConnectWorkflowOutcome = z.infer<
|
|
277
|
+
typeof HrisConnectWorkflowOutcomeSchema
|
|
278
|
+
>;
|
|
279
|
+
export type ReanalyzeWorkflowOutcome = z.infer<
|
|
280
|
+
typeof ReanalyzeWorkflowOutcomeSchema
|
|
281
|
+
>;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The outcome of one terminal transition of any registered workflow type.
|
|
285
|
+
* Validate untrusted values (persisted jsonb, wire parts) with
|
|
286
|
+
* {@link WorkflowOutcomeSchema}.
|
|
287
|
+
*/
|
|
288
|
+
export const WorkflowOutcomeSchema = z.union([
|
|
289
|
+
DiscoverWorkflowOutcomeSchema,
|
|
290
|
+
SynthesizeWorkflowOutcomeSchema,
|
|
291
|
+
HrisConnectWorkflowOutcomeSchema,
|
|
292
|
+
ReanalyzeWorkflowOutcomeSchema,
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
export type WorkflowOutcome = z.infer<typeof WorkflowOutcomeSchema>;
|
|
296
|
+
|
|
297
|
+
/** The outcome member for one workflow type. */
|
|
298
|
+
export type WorkflowOutcomeOf<T extends WorkflowOutcome["workflowType"]> =
|
|
299
|
+
Extract<WorkflowOutcome, { workflowType: T }>;
|