@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.
@@ -94,6 +94,7 @@ export const openApiRoutes = {
94
94
  '/api/factory/floor': ['GET'],
95
95
  '/api/factory/kpis': ['GET'],
96
96
  '/api/factory/quality/changes': ['POST'],
97
+ '/api/factory/quality/corpus': ['GET'],
97
98
  '/api/factory/quality/deployments': ['POST'],
98
99
  '/api/factory/quality/plans/{planId}': ['GET'],
99
100
  '/api/factory/quality/plans/{planId}/decision': ['GET'],
@@ -102,6 +103,19 @@ export const openApiRoutes = {
102
103
  '/api/internal-admin/impersonate/end': ['POST'],
103
104
  '/api/internal-admin/impersonate/session': ['GET'],
104
105
  '/api/internal-admin/impersonate/start': ['POST'],
106
+ '/api/internal-admin/quality/blocked': ['GET'],
107
+ '/api/internal-admin/quality/candidates': ['GET'],
108
+ '/api/internal-admin/quality/candidates/{id}': ['GET'],
109
+ '/api/internal-admin/quality/candidates/{id}/accept': ['POST'],
110
+ '/api/internal-admin/quality/candidates/{id}/reject': ['POST'],
111
+ '/api/internal-admin/quality/changes/manual': ['POST'],
112
+ '/api/internal-admin/quality/escalations': ['GET'],
113
+ '/api/internal-admin/quality/escalations/{id}/resolve': ['POST'],
114
+ '/api/internal-admin/quality/plans/{planId}/status': ['GET'],
115
+ '/api/internal-admin/quality/references/floor': ['PUT'],
116
+ '/api/internal-admin/quality/spend': ['GET'],
117
+ '/api/internal-admin/quality/subjects': ['GET'],
118
+ '/api/internal-admin/quality/subjects/{subjectKind}/{subjectKey}/timeline': ['GET'],
105
119
  '/api/me': ['GET'],
106
120
  '/api/me/access-list': ['GET'],
107
121
  '/api/me/action-items': ['GET'],
@@ -156,6 +170,7 @@ export const openApiRoutes = {
156
170
  '/api/org/cancel-deletion': ['POST'],
157
171
  '/api/org/delete': ['POST'],
158
172
  '/api/org/deletion-eligibility': ['GET'],
173
+ '/api/org/improve-with-usage': ['GET', 'PUT'],
159
174
  '/api/org/reconciliation': ['GET'],
160
175
  '/api/org/reconciliation/divergences/{divergenceId}/resolution': ['POST'],
161
176
  '/api/org/structure-findings': ['GET'],
@@ -91,6 +91,9 @@ Canonical vocabulary for structured assistant message output. Defines the type s
91
91
  - `UndoResultData` _(type)_ — Undo result data payload.
92
92
  - `UndoResultDataPart` _(type)_ — Undo result data part (wire format).
93
93
  - `WireSurfaceBuilder` — Factory for creating wire-format surface parts.
94
+ - `WorkflowOutcomeData` _(type)_ — Workflow outcome data payload: which workflow, and how its epoch ended.
95
+ - `WorkflowOutcomeDataPart` _(type)_ — Workflow outcome data part (wire format, as persisted in `chat_messages.parts`).
96
+ - `WorkflowOutcomePart` _(type)_ — Workflow outcome message part (semantic type, as rendered).
94
97
  - `WorkflowProgressData` _(type)_ — Workflow progress surface data payload — the anchor's handle and its discriminator.
95
98
  - `WorkflowProgressDataPart` _(type)_ — Workflow progress data part (wire format, as persisted in `chat_messages.parts`).
96
99
  - `WorkflowProgressPart` _(type)_ — Workflow progress message part (semantic type, as rendered).
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { WireSurfaceBuilder } from "../wire";
4
+ import type { WorkflowOutcomeData } from "../workflow-outcome";
5
+
6
+ describe("WireSurfaceBuilder.workflowOutcome", () => {
7
+ const data: WorkflowOutcomeData = {
8
+ workflowId: "3f1c2d4e-0000-0000-0000-000000000000",
9
+ outcome: {
10
+ workflowType: "source-sync/discover",
11
+ executionEpoch: 0,
12
+ kind: "empty",
13
+ reason: "nothing_found",
14
+ facts: {
15
+ probesTotal: 12,
16
+ probesDone: 12,
17
+ filesSeen: 0,
18
+ filesShown: 0,
19
+ filesHidden: 0,
20
+ filesDropped: 0,
21
+ coveragePercent: 100,
22
+ },
23
+ disposition: { kind: "narrate" },
24
+ },
25
+ };
26
+
27
+ it("returns a data-workflow-outcome part carrying the data verbatim", () => {
28
+ const part = WireSurfaceBuilder.workflowOutcome(data);
29
+ expect(part.type).toBe("data-workflow-outcome");
30
+ expect(part.data).toEqual(data);
31
+ });
32
+
33
+ // Non-governed: nothing on the payload makes it count against the
34
+ // at-most-one-governed-surface-per-turn budget.
35
+ it("carries no governed-surface fields", () => {
36
+ const part = WireSurfaceBuilder.workflowOutcome(data);
37
+ expect(Object.keys(part.data).sort()).toEqual(["outcome", "workflowId"]);
38
+ expect(part.data).not.toHaveProperty("executionId");
39
+ expect(part.data).not.toHaveProperty("submitEndpoint");
40
+ });
41
+ });
@@ -90,6 +90,13 @@ export type {
90
90
  WorkflowProgressDataPart,
91
91
  } from "./workflow-progress";
92
92
 
93
+ // Workflow outcome types (non-governed, the frozen outcome on a settlement message)
94
+ export type {
95
+ WorkflowOutcomeData,
96
+ WorkflowOutcomePart,
97
+ WorkflowOutcomeDataPart,
98
+ } from "./workflow-outcome";
99
+
93
100
  // Execution result types
94
101
  export type {
95
102
  ExecutionArtifactStatus,
@@ -21,6 +21,7 @@ import type { StructureReviewPart } from "./structure-review";
21
21
  import type { InputRequestPart } from "./input-request";
22
22
  import type { SuggestedRepliesPart } from "./suggested-replies";
23
23
  import type { WorkflowProgressPart } from "./workflow-progress";
24
+ import type { WorkflowOutcomePart } from "./workflow-outcome";
24
25
 
25
26
  // =============================================================================
26
27
  // Narrative Parts (Streamable)
@@ -125,6 +126,10 @@ export interface TablePart {
125
126
  * background workflow to the occurrence and presents no decision, so it must
126
127
  * not consume the governed-surface budget either.
127
128
  *
129
+ * WorkflowOutcomePart is the third non-governed member: the frozen outcome of a
130
+ * workflow's execution epoch, carried on its settlement message. It presents no
131
+ * decision; a review disposition is delivered as its own InputRequestPart.
132
+ *
128
133
  * InputRequestPart is GOVERNED, like StructureReviewPart: it anchors a reviewed
129
134
  * request that asks the person to decide, so it counts against the
130
135
  * at-most-one-governed-surface-per-turn budget. It is the generic anchor;
@@ -142,7 +147,8 @@ export type SurfacePart =
142
147
  | StructureReviewPart
143
148
  | InputRequestPart
144
149
  | SuggestedRepliesPart
145
- | WorkflowProgressPart;
150
+ | WorkflowProgressPart
151
+ | WorkflowOutcomePart;
146
152
 
147
153
  /**
148
154
  * All assistant message part types.
@@ -35,6 +35,10 @@ import type {
35
35
  WorkflowProgressData,
36
36
  WorkflowProgressDataPart,
37
37
  } from "./workflow-progress";
38
+ import type {
39
+ WorkflowOutcomeData,
40
+ WorkflowOutcomeDataPart,
41
+ } from "./workflow-outcome";
38
42
  import type {
39
43
  ExecutionResultData,
40
44
  ExecutionResultDataPart,
@@ -246,6 +250,28 @@ export const WireSurfaceBuilder = {
246
250
  };
247
251
  },
248
252
 
253
+ /**
254
+ * Build a workflow-outcome data part for a settlement message.
255
+ * The frozen outcome of one execution epoch, written once at settlement
256
+ * (control ADR-CTRL-540).
257
+ *
258
+ * INVARIANTS:
259
+ * - Not a governed surface: presents no decision
260
+ * - The outcome is the classified, immutable value; nothing re-derives it
261
+ * - Counts and closed codes only, so it is safe on share-visible parts
262
+ * - Emission is orchestration-driven (settlement delivery), never a model
263
+ * choice
264
+ *
265
+ * @param data - Workflow outcome data (workflowId + frozen outcome)
266
+ * @returns Wire-format workflow outcome part
267
+ */
268
+ workflowOutcome(data: WorkflowOutcomeData): WorkflowOutcomeDataPart {
269
+ return {
270
+ type: "data-workflow-outcome",
271
+ data,
272
+ };
273
+ },
274
+
249
275
  /**
250
276
  * Build an execution result data part for streaming.
251
277
  * Reports the outcome of an executed action.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Workflow Outcome Part Types
3
+ *
4
+ * The settled outcome of one execution epoch of a durable background workflow,
5
+ * carried on the settlement message the server writes into the conversation
6
+ * that launched the workflow (control ADR-CTRL-540). It is the durable,
7
+ * conversation-level EVIDENCE of how the run ended: the deterministic narration
8
+ * beside it is built from exactly this value, and a later turn ("what
9
+ * happened?") is grounded in it.
10
+ *
11
+ * WORKFLOW OUTCOME INVARIANTS:
12
+ * - WRITTEN ONCE, AT SETTLEMENT. Unlike `data-workflow-progress`, which is
13
+ * minted at t=0 and so must carry no result, this part is minted only after
14
+ * the outcome is frozen, on a message of its own. It can never be stale.
15
+ * - THE FROZEN OUTCOME, VERBATIM. `outcome` is the value the backend classified
16
+ * atomically with the terminal transition. Nothing re-derives it.
17
+ * - SHARE-SAFE. Chat parts reach token-only share readers verbatim, so the
18
+ * outcome holds counts and closed codes only (see `../workflows/outcome.ts`).
19
+ * - NON-GOVERNED. It presents no decision: no `executionId`, no
20
+ * `submitEndpoint`. A `review` disposition is delivered as its own input
21
+ * request anchor, not through this part.
22
+ * - Emission is orchestration-driven (settlement delivery), never a model
23
+ * choice.
24
+ *
25
+ * Purely additive: an app that predates this part normalizes unknown part types
26
+ * to null, drops it, and still renders the narration text.
27
+ *
28
+ * @see ../workflows/outcome.ts for the outcome vocabulary
29
+ */
30
+
31
+ import type { WorkflowOutcome } from "../workflows/outcome";
32
+
33
+ /** Workflow outcome data payload: which workflow, and how its epoch ended. */
34
+ export interface WorkflowOutcomeData {
35
+ /** The durable workflow the outcome settles. */
36
+ workflowId: string;
37
+ /** The frozen, typed outcome (carries its workflow type and epoch). */
38
+ outcome: WorkflowOutcome;
39
+ }
40
+
41
+ /** Workflow outcome message part (semantic type, as rendered). */
42
+ export interface WorkflowOutcomePart {
43
+ type: "workflow-outcome";
44
+ data: WorkflowOutcomeData;
45
+ }
46
+
47
+ /**
48
+ * Workflow outcome data part (wire format, as persisted in
49
+ * `chat_messages.parts`). Uses AI SDK's data-{name} convention.
50
+ */
51
+ export interface WorkflowOutcomeDataPart {
52
+ type: "data-workflow-outcome";
53
+ data: WorkflowOutcomeData;
54
+ }
@@ -93,6 +93,10 @@ export type {
93
93
  WorkflowProgressData,
94
94
  WorkflowProgressPart,
95
95
  WorkflowProgressDataPart,
96
+ // Workflow outcome types (non-governed, frozen outcome on a settlement message, ADR-CTRL-540)
97
+ WorkflowOutcomeData,
98
+ WorkflowOutcomePart,
99
+ WorkflowOutcomeDataPart,
96
100
  // Execution result types (Phase 5)
97
101
  ExecutionArtifactStatus,
98
102
  ExecutionResultSummary,
@@ -216,3 +216,39 @@ export type {
216
216
  export { ANALYSIS_WORKFLOW_TYPES, isWorkflowType } from "../workflows/index";
217
217
 
218
218
  export type { WorkflowType } from "../workflows/index";
219
+
220
+ // The typed outcome of a workflow's terminal transition (control ADR-CTRL-540):
221
+ // per-type closed reasons, typed facts and an explicit disposition, classified
222
+ // once by the backend and frozen. See src/workflows/outcome.ts.
223
+ export {
224
+ DISCOVER_OUTCOME_KIND_OF_REASON,
225
+ DiscoverOutcomeFactsSchema,
226
+ DiscoverWorkflowOutcomeSchema,
227
+ HrisConnectWorkflowOutcomeSchema,
228
+ ORG_STRUCTURE_OUTCOME_KIND_OF_REASON,
229
+ OrgStructureOutcomeFactsSchema,
230
+ ReanalyzeWorkflowOutcomeSchema,
231
+ SYNTHESIZE_OUTCOME_KIND_OF_REASON,
232
+ SynthesizeOutcomeFactsSchema,
233
+ SynthesizeWorkflowOutcomeSchema,
234
+ WORKFLOW_OUTCOME_KINDS,
235
+ WorkflowOutcomeDispositionSchema,
236
+ WorkflowOutcomeSchema,
237
+ } from "../workflows/index";
238
+
239
+ export type {
240
+ DiscoverOutcomeFacts,
241
+ DiscoverOutcomeReason,
242
+ DiscoverWorkflowOutcome,
243
+ HrisConnectWorkflowOutcome,
244
+ OrgStructureOutcomeFacts,
245
+ OrgStructureOutcomeReason,
246
+ ReanalyzeWorkflowOutcome,
247
+ SynthesizeOutcomeFacts,
248
+ SynthesizeOutcomeReason,
249
+ SynthesizeWorkflowOutcome,
250
+ WorkflowOutcome,
251
+ WorkflowOutcomeDisposition,
252
+ WorkflowOutcomeKind,
253
+ WorkflowOutcomeOf,
254
+ } from "../workflows/index";
@@ -32,7 +32,23 @@ so every producer, validator and renderer agrees on it.
32
32
  - **Source Sync registers two flows.** `source-sync/discover` (Stage 1)
33
33
  retains nothing; `source-sync/synthesize` (Stage 4) runs only after a person
34
34
  approved the exact set it reads.
35
+ - **Every type has a typed outcome** (`outcome.ts`, control ADR-CTRL-540). A
36
+ terminal transition is classified once, by the backend, into a per-type
37
+ member of `WorkflowOutcome`: a closed `reason`, typed count-only `facts`, a
38
+ `kind` that is a function of the reason (`*_OUTCOME_KIND_OF_REASON`), and an
39
+ explicit `disposition` (`review` or `narrate`). Adding a workflow type means
40
+ adding its outcome member; the union must cover every registered type.
41
+ - **`unknown` is a safety valve, not a status.** It names an unrecognized
42
+ terminal transition, never a workflow whose state could not be read.
43
+
44
+ ## Public API
45
+
46
+ - `ANALYSIS_WORKFLOW_TYPES`, `WorkflowType`, `isWorkflowType` (`types.ts`).
47
+ - `WorkflowOutcomeSchema` / `WorkflowOutcome` and the per-type member schemas,
48
+ reason tables, fact schemas and `WorkflowOutcomeDispositionSchema`
49
+ (`outcome.ts`).
35
50
 
36
51
  ## Dependencies
37
52
 
38
- None. Pure vocabulary data and one pure guard; no `zod`, no runtime imports.
53
+ `zod` (outcome schemas) and `../input-requests/types` (the review
54
+ disposition's request type). Otherwise pure vocabulary data and one guard.
@@ -13,6 +13,12 @@ wire value against it.
13
13
  rejections: a bare `hris-connect` without its namespace, the namespace on
14
14
  its own, a trailing separator, an unknown flow, and non-string inputs.
15
15
 
16
+ - `outcome.test.ts` — the outcome union covers every registered workflow type
17
+ (compile-time), each reason table's kinds are valid, the schema accepts a
18
+ well-formed member and refuses a kind that does not match its reason, a
19
+ review disposition on a non-produced outcome, a reason from another type,
20
+ and extra keys (the facts stay closed).
21
+
16
22
  ## Invariants
17
23
 
18
24
  - These assert VOCABULARY and SHAPE, never behavior. What a workflow of a
@@ -0,0 +1,130 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import type { WorkflowType } from "../types";
4
+ import {
5
+ DISCOVER_OUTCOME_KIND_OF_REASON,
6
+ ORG_STRUCTURE_OUTCOME_KIND_OF_REASON,
7
+ SYNTHESIZE_OUTCOME_KIND_OF_REASON,
8
+ WORKFLOW_OUTCOME_KINDS,
9
+ WorkflowOutcomeSchema,
10
+ type WorkflowOutcome,
11
+ } from "../outcome";
12
+
13
+ // Compile-time: every registered workflow type has an outcome member, and the
14
+ // union names no type that is not registered.
15
+ type MissingOutcome = Exclude<WorkflowType, WorkflowOutcome["workflowType"]>;
16
+ type UnregisteredOutcome = Exclude<
17
+ WorkflowOutcome["workflowType"],
18
+ WorkflowType
19
+ >;
20
+ const coversEveryType: [MissingOutcome, UnregisteredOutcome] extends [
21
+ never,
22
+ never,
23
+ ]
24
+ ? true
25
+ : false = true;
26
+
27
+ const discoverFacts = {
28
+ probesTotal: 12,
29
+ probesDone: 12,
30
+ filesSeen: 0,
31
+ filesShown: 0,
32
+ filesHidden: 0,
33
+ filesDropped: 0,
34
+ coveragePercent: 100,
35
+ };
36
+
37
+ const empty = {
38
+ workflowType: "source-sync/discover",
39
+ executionEpoch: 0,
40
+ kind: "empty",
41
+ reason: "nothing_found",
42
+ facts: discoverFacts,
43
+ disposition: { kind: "narrate" },
44
+ } as const;
45
+
46
+ describe("WorkflowOutcome", () => {
47
+ it("covers every registered workflow type", () => {
48
+ expect(coversEveryType).toBe(true);
49
+ });
50
+
51
+ it("maps every reason to a known kind", () => {
52
+ for (const table of [
53
+ DISCOVER_OUTCOME_KIND_OF_REASON,
54
+ SYNTHESIZE_OUTCOME_KIND_OF_REASON,
55
+ ORG_STRUCTURE_OUTCOME_KIND_OF_REASON,
56
+ ]) {
57
+ for (const kind of Object.values(table)) {
58
+ expect(WORKFLOW_OUTCOME_KINDS).toContain(kind);
59
+ }
60
+ // Every table keeps the two safety endings.
61
+ expect(table).toHaveProperty("abandoned", "abandoned");
62
+ expect(table).toHaveProperty("unrecognized_terminal_state", "unknown");
63
+ }
64
+ });
65
+
66
+ it("accepts a well-formed member", () => {
67
+ expect(WorkflowOutcomeSchema.safeParse(empty).success).toBe(true);
68
+ });
69
+
70
+ it("accepts null facts (the execution reported none)", () => {
71
+ const abandoned = {
72
+ ...empty,
73
+ kind: "abandoned",
74
+ reason: "abandoned",
75
+ facts: null,
76
+ };
77
+ expect(WorkflowOutcomeSchema.safeParse(abandoned).success).toBe(true);
78
+ });
79
+
80
+ it("refuses a kind that does not match its reason", () => {
81
+ expect(
82
+ WorkflowOutcomeSchema.safeParse({ ...empty, kind: "produced" }).success,
83
+ ).toBe(false);
84
+ });
85
+
86
+ it("refuses a review disposition on an outcome that produced nothing", () => {
87
+ const reviewEmpty = {
88
+ ...empty,
89
+ disposition: {
90
+ kind: "review",
91
+ requestType: "source-sync/candidates",
92
+ requestId: "set-1",
93
+ },
94
+ };
95
+ expect(WorkflowOutcomeSchema.safeParse(reviewEmpty).success).toBe(false);
96
+ });
97
+
98
+ it("accepts a review disposition on a produced outcome", () => {
99
+ const produced = {
100
+ ...empty,
101
+ kind: "produced",
102
+ reason: "candidates_found",
103
+ disposition: {
104
+ kind: "review",
105
+ requestType: "source-sync/candidates",
106
+ requestId: "set-1",
107
+ },
108
+ };
109
+ expect(WorkflowOutcomeSchema.safeParse(produced).success).toBe(true);
110
+ });
111
+
112
+ it("refuses a reason that belongs to another workflow type", () => {
113
+ expect(
114
+ WorkflowOutcomeSchema.safeParse({
115
+ ...empty,
116
+ kind: "produced",
117
+ reason: "proposal_ready",
118
+ }).success,
119
+ ).toBe(false);
120
+ });
121
+
122
+ it("keeps facts closed: an extra key is refused", () => {
123
+ expect(
124
+ WorkflowOutcomeSchema.safeParse({
125
+ ...empty,
126
+ facts: { ...discoverFacts, fileNames: 3 },
127
+ }).success,
128
+ ).toBe(false);
129
+ });
130
+ });
@@ -10,3 +10,36 @@
10
10
  export type { WorkflowType } from "./types";
11
11
 
12
12
  export { ANALYSIS_WORKFLOW_TYPES, isWorkflowType } from "./types";
13
+
14
+ export type {
15
+ DiscoverOutcomeFacts,
16
+ DiscoverOutcomeReason,
17
+ DiscoverWorkflowOutcome,
18
+ HrisConnectWorkflowOutcome,
19
+ OrgStructureOutcomeFacts,
20
+ OrgStructureOutcomeReason,
21
+ ReanalyzeWorkflowOutcome,
22
+ SynthesizeOutcomeFacts,
23
+ SynthesizeOutcomeReason,
24
+ SynthesizeWorkflowOutcome,
25
+ WorkflowOutcome,
26
+ WorkflowOutcomeDisposition,
27
+ WorkflowOutcomeKind,
28
+ WorkflowOutcomeOf,
29
+ } from "./outcome";
30
+
31
+ export {
32
+ DISCOVER_OUTCOME_KIND_OF_REASON,
33
+ DiscoverOutcomeFactsSchema,
34
+ DiscoverWorkflowOutcomeSchema,
35
+ HrisConnectWorkflowOutcomeSchema,
36
+ ORG_STRUCTURE_OUTCOME_KIND_OF_REASON,
37
+ OrgStructureOutcomeFactsSchema,
38
+ ReanalyzeWorkflowOutcomeSchema,
39
+ SYNTHESIZE_OUTCOME_KIND_OF_REASON,
40
+ SynthesizeOutcomeFactsSchema,
41
+ SynthesizeWorkflowOutcomeSchema,
42
+ WORKFLOW_OUTCOME_KINDS,
43
+ WorkflowOutcomeDispositionSchema,
44
+ WorkflowOutcomeSchema,
45
+ } from "./outcome";