@company-semantics/contracts 88.2.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 +4769 -3479
- package/src/generated/openapi-routes.ts +19 -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/source-sync/README.md +44 -2
- package/src/source-sync/__tests__/barrel.test.ts +11 -1
- package/src/source-sync/__tests__/schemas.test.ts +141 -2
- package/src/source-sync/__tests__/scope-projection.test.ts +1 -1
- package/src/source-sync/__tests__/vocabulary.test.ts +73 -1
- package/src/source-sync/candidate-item.ts +155 -0
- package/src/source-sync/candidate-totals.ts +2 -4
- package/src/source-sync/index.ts +13 -4
- package/src/source-sync/relevance.ts +38 -3
- package/src/source-sync/schemas.ts +5 -127
- package/src/source-sync/vocabulary.ts +45 -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
|
@@ -93,11 +93,29 @@ export const openApiRoutes = {
|
|
|
93
93
|
'/api/executions/{executionId}/undo': ['POST'],
|
|
94
94
|
'/api/factory/floor': ['GET'],
|
|
95
95
|
'/api/factory/kpis': ['GET'],
|
|
96
|
+
'/api/factory/quality/changes': ['POST'],
|
|
97
|
+
'/api/factory/quality/corpus': ['GET'],
|
|
98
|
+
'/api/factory/quality/deployments': ['POST'],
|
|
99
|
+
'/api/factory/quality/plans/{planId}': ['GET'],
|
|
100
|
+
'/api/factory/quality/plans/{planId}/decision': ['GET'],
|
|
96
101
|
'/api/factory/snapshot': ['GET'],
|
|
97
102
|
'/api/ingestion/operations/{id}': ['GET'],
|
|
98
103
|
'/api/internal-admin/impersonate/end': ['POST'],
|
|
99
104
|
'/api/internal-admin/impersonate/session': ['GET'],
|
|
100
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'],
|
|
101
119
|
'/api/me': ['GET'],
|
|
102
120
|
'/api/me/access-list': ['GET'],
|
|
103
121
|
'/api/me/action-items': ['GET'],
|
|
@@ -152,6 +170,7 @@ export const openApiRoutes = {
|
|
|
152
170
|
'/api/org/cancel-deletion': ['POST'],
|
|
153
171
|
'/api/org/delete': ['POST'],
|
|
154
172
|
'/api/org/deletion-eligibility': ['GET'],
|
|
173
|
+
'/api/org/improve-with-usage': ['GET', 'PUT'],
|
|
155
174
|
'/api/org/reconciliation': ['GET'],
|
|
156
175
|
'/api/org/reconciliation/divergences/{divergenceId}/resolution': ['POST'],
|
|
157
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";
|
|
@@ -134,7 +134,7 @@ Doctrine: control ADR `source-discovery-relevance-funnel`. Vocabulary: ADR
|
|
|
134
134
|
`unclassified` (so `sourceClass` and `recommendedUse` are both null) or
|
|
135
135
|
`classified` (both non-null). A shown line can still be
|
|
136
136
|
`pending_classification` in its `machineState`.
|
|
137
|
-
- **Reasons are a discriminated union.** `relevanceReasons` (≤
|
|
137
|
+
- **Reasons are a discriminated union.** `relevanceReasons` (≤ 16) is
|
|
138
138
|
`RelevanceReason`: `lexicon` (a product-owned `term` and a `count`),
|
|
139
139
|
`signal` (a `value` or null), `evaluation` (a code only) or `model` (a
|
|
140
140
|
`score` or null). Each kind has its own disjoint code set, and the app
|
|
@@ -155,6 +155,42 @@ Doctrine: control ADR `source-discovery-relevance-funnel`. Vocabulary: ADR
|
|
|
155
155
|
`filesNotRediscovered` and `filesPendingClassification`, counted by
|
|
156
156
|
`candidateTotalsOf` over distinct files.
|
|
157
157
|
|
|
158
|
+
## Document type
|
|
159
|
+
|
|
160
|
+
Doctrine: control ADR `source-discovery-observation-vs-policy`. Vocabulary: ADR
|
|
161
|
+
`source-discovery-document-type-vocabulary`. The model NAMES what a source is;
|
|
162
|
+
product policy in the backend decides what that means.
|
|
163
|
+
|
|
164
|
+
- **Structural, never a verdict.** `DOCUMENT_TYPES` (in `vocabulary.ts`) says
|
|
165
|
+
what a file IS — `corporate_formation_record`, never `irrelevant_doc` —
|
|
166
|
+
never how relevant it is. A medium is not a meaning: `image_or_screenshot`
|
|
167
|
+
is graded on its content. `unknown` is a real answer (the model could not
|
|
168
|
+
tell, or two reads disagreed) and is always last.
|
|
169
|
+
- **The resolved type travels, nothing else.** The candidate item's
|
|
170
|
+
`documentType` and `documentTypeConfidence` (0..1) carry the RESOLVED type,
|
|
171
|
+
after the backend reconciles the T1 and T2 observations. Both are null on an
|
|
172
|
+
unclassified line and on every line of a run pinned before the typed path
|
|
173
|
+
(v1), where `sourceClass` alone is set. A known type always carries a
|
|
174
|
+
confidence; `unknown` and null never do. Per-tier types and per-layer scores
|
|
175
|
+
are backend evidence and stay off the wire.
|
|
176
|
+
- **Policy lives in the backend.** Families, relevance policy modes, ceilings,
|
|
177
|
+
confidence floors, strategy signals and peek verdicts are backend product
|
|
178
|
+
policy, never contracts vocabulary
|
|
179
|
+
(INV-SOURCESYNC-OBSERVATION-IS-SEPARATE-FROM-POLICY). Changing what a type
|
|
180
|
+
means is a backend change, not a release.
|
|
181
|
+
- **Same word, separate axis.** Five members are also words in other sets:
|
|
182
|
+
`board_deck`, `board_minutes`, `investor_update`, `roadmap`
|
|
183
|
+
(`SOURCE_CLASSES`) and `unknown` (`SHARING_BREADTHS`). `DOCUMENT_TYPES`
|
|
184
|
+
therefore sits outside the cross-set disjointness check, and its overlap is
|
|
185
|
+
pinned to exactly those five.
|
|
186
|
+
- **Typed-relevance reasons.** Model codes `type_policy` (the policy-applied
|
|
187
|
+
score of the resolved type) and `peek_strategy_evidence_present` /
|
|
188
|
+
`peek_strategy_evidence_absent`. Evaluation codes
|
|
189
|
+
`peek_insufficient_evidence`, `type_disagreement`, `type_below_confidence`
|
|
190
|
+
and `execution_failed_t1` / `execution_failed_t2`: a tier that did not run
|
|
191
|
+
is uncertainty, never irrelevance. `peek_confirms` / `peek_contradicts`
|
|
192
|
+
stay for v1-pinned rows.
|
|
193
|
+
|
|
158
194
|
## Public API
|
|
159
195
|
|
|
160
196
|
- `SOURCE_CLASSES` / `SourceClass` — what kind of document a source is.
|
|
@@ -172,10 +208,14 @@ Doctrine: control ADR `source-discovery-relevance-funnel`. Vocabulary: ADR
|
|
|
172
208
|
`pending`, `approved` or `rejected` (no verb writes `rejected` yet).
|
|
173
209
|
- `SOURCE_SYNC_SCOPE_APPROVER_KINDS` / `SourceSyncScopeApproverKind` —
|
|
174
210
|
`org_admin` or `unit_head`.
|
|
211
|
+
- `DOCUMENT_TYPES` / `DocumentType` / `KnownDocumentType` — what a source is,
|
|
212
|
+
structurally; `KnownDocumentType` excludes `unknown` (see Document type).
|
|
175
213
|
- `SourceSyncDiscoveryRunResponseSchema` / `SourceSyncDiscoveryRunResponse` —
|
|
176
214
|
the discovery run started, and the workflow narrating it.
|
|
177
215
|
- `SourceSyncCandidateItemSchema` / `SourceSyncCandidateItem` — one source
|
|
178
|
-
proposed for one target document, with the server's `included
|
|
216
|
+
proposed for one target document, with the server's `included` and the
|
|
217
|
+
resolved `documentType` / `documentTypeConfidence` (null when unclassified
|
|
218
|
+
or v1-pinned).
|
|
179
219
|
- `SourceSyncCandidateSetResponseSchema` / `SourceSyncCandidateSetResponse` —
|
|
180
220
|
the draft set under review: its revision, items, presented questions,
|
|
181
221
|
distinct-file `totals` and cost estimate.
|
|
@@ -267,5 +307,7 @@ Doctrine: control ADR `source-discovery-relevance-funnel`. Vocabulary: ADR
|
|
|
267
307
|
|
|
268
308
|
`rules.ts` imports only `zod`, `../api/primitives` and `./vocabulary`.
|
|
269
309
|
`upkeep.ts` and `reach.ts` import only `zod` and `../api/primitives`.
|
|
310
|
+
`candidate-item.ts` imports only `zod`, `../api/primitives`, `./vocabulary`
|
|
311
|
+
and `./relevance`.
|
|
270
312
|
`relevance.ts` imports only `zod`.
|
|
271
313
|
`vocabulary.ts` itself has no imports.
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* standing orders and the Drive-reach shapes.
|
|
19
19
|
* - Every runtime name `relevance.ts` declares is reachable from the root as
|
|
20
20
|
* the same binding.
|
|
21
|
+
* - `DOCUMENT_TYPES` is reachable from the root as the same binding, and its
|
|
22
|
+
* two types are named by the barrel.
|
|
21
23
|
* - `ACTION_ITEM_KINDS` at the root carries `sourceSync.approval_pending`.
|
|
22
24
|
* - The root re-exports the domain barrel explicitly.
|
|
23
25
|
*
|
|
@@ -67,18 +69,20 @@ import type {
|
|
|
67
69
|
SourceSyncStandingOrder,
|
|
68
70
|
SourceSyncStandingOrdersResponse,
|
|
69
71
|
} from "../../index";
|
|
72
|
+
import * as candidateItem from "../candidate-item";
|
|
70
73
|
import * as reach from "../reach";
|
|
71
74
|
import * as relevance from "../relevance";
|
|
72
75
|
import * as rules from "../rules";
|
|
73
76
|
import * as schemas from "../schemas";
|
|
74
77
|
import * as upkeep from "../upkeep";
|
|
78
|
+
import * as vocabulary from "../vocabulary";
|
|
75
79
|
|
|
76
80
|
/** Runtime names Release C adds, keyed to the module that declares them. */
|
|
77
81
|
const RELEASE_C_RUNTIME_NAMES: ReadonlyArray<
|
|
78
82
|
readonly [name: string, module: Record<string, unknown>]
|
|
79
83
|
> = [
|
|
80
84
|
["SOURCE_SYNC_RISK_REASONS", schemas],
|
|
81
|
-
["SourceSyncCandidateItemPatchSchema",
|
|
85
|
+
["SourceSyncCandidateItemPatchSchema", candidateItem],
|
|
82
86
|
["SourceSyncAddCandidateRequestSchema", schemas],
|
|
83
87
|
["SourceSyncApproveRequestSchema", schemas],
|
|
84
88
|
["SourceSyncScopeApprovalSchema", schemas],
|
|
@@ -250,6 +254,12 @@ describe("source-sync Release C package surface", () => {
|
|
|
250
254
|
}
|
|
251
255
|
});
|
|
252
256
|
|
|
257
|
+
it("every document-type name is reachable from the root as the same binding", () => {
|
|
258
|
+
expect(rootBarrel.DOCUMENT_TYPES).toBe(vocabulary.DOCUMENT_TYPES);
|
|
259
|
+
expect([...barrelTypeExports]).toContain("DocumentType");
|
|
260
|
+
expect([...barrelTypeExports]).toContain("KnownDocumentType");
|
|
261
|
+
});
|
|
262
|
+
|
|
253
263
|
it("exposes sourceSync.approval_pending in ACTION_ITEM_KINDS at the root", () => {
|
|
254
264
|
expect(rootBarrel.ACTION_ITEM_KINDS).toContain(
|
|
255
265
|
"sourceSync.approval_pending",
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* - Relevance and treatment are two axes (control ADR source-discovery-relevance-funnel):
|
|
15
15
|
* a hidden line is never included; an unclassified line has no recommendation and
|
|
16
16
|
* no class; `selectionSource` is present exactly when `use` is.
|
|
17
|
+
* - The resolved document type (control ADR source-discovery-observation-vs-policy)
|
|
18
|
+
* is a REQUIRED nullable key: an unclassified line has none, and a confidence
|
|
19
|
+
* travels exactly with a known type. A line carries at most sixteen reasons.
|
|
17
20
|
* - A relevance reason's code belongs to its kind's closed set.
|
|
18
21
|
* - A candidate patch changes exactly one thing: bringing a line into review never
|
|
19
22
|
* also commits a treatment, and a patch can never hide a line.
|
|
@@ -28,6 +31,8 @@ import {
|
|
|
28
31
|
SourceSyncCandidateSetResponseSchema,
|
|
29
32
|
SourceSyncDiscoveryRunResponseSchema,
|
|
30
33
|
} from "../index";
|
|
34
|
+
import * as root from "../../index";
|
|
35
|
+
import * as candidateItemModule from "../candidate-item";
|
|
31
36
|
|
|
32
37
|
const RUN_ID = "3f1a2b4c-5d6e-4f70-8192-a3b4c5d6e7f8";
|
|
33
38
|
const WORKFLOW_ID = "4a2b3c5d-6e7f-4a81-9b2c-b4c5d6e7f8a9";
|
|
@@ -52,6 +57,8 @@ function candidateItem(overrides: Record<string, unknown> = {}) {
|
|
|
52
57
|
visibility: "shown",
|
|
53
58
|
classificationState: "classified",
|
|
54
59
|
sourceClass: "board_deck",
|
|
60
|
+
documentType: null,
|
|
61
|
+
documentTypeConfidence: null,
|
|
55
62
|
periodLabel: "Q3 2026",
|
|
56
63
|
seriesRole: "current",
|
|
57
64
|
relevance: 0.92,
|
|
@@ -142,6 +149,18 @@ function candidateSet(overrides: Record<string, unknown> = {}) {
|
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
describe("SourceSyncCandidateItemSchema", () => {
|
|
152
|
+
it("the candidate item schema moved without changing what it accepts", () => {
|
|
153
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(candidateItem());
|
|
154
|
+
expect(parsed.success).toBe(true);
|
|
155
|
+
expect(parsed.data).toEqual(candidateItem());
|
|
156
|
+
expect(root.SourceSyncCandidateItemSchema).toBe(
|
|
157
|
+
candidateItemModule.SourceSyncCandidateItemSchema,
|
|
158
|
+
);
|
|
159
|
+
expect(root.SourceSyncCandidateItemPatchSchema).toBe(
|
|
160
|
+
candidateItemModule.SourceSyncCandidateItemPatchSchema,
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
145
164
|
it("refuses a candidate item with no audience fit key at all", () => {
|
|
146
165
|
const { audienceFit: _omitted, ...item } = candidateItem();
|
|
147
166
|
const parsed = SourceSyncCandidateItemSchema.safeParse(item);
|
|
@@ -362,15 +381,104 @@ describe("candidate relevance", () => {
|
|
|
362
381
|
]);
|
|
363
382
|
});
|
|
364
383
|
|
|
365
|
-
it("
|
|
384
|
+
it("accepts sixteen reasons", () => {
|
|
366
385
|
const reason = { kind: "evaluation", code: "explored_t1" };
|
|
367
386
|
expect(
|
|
368
387
|
SourceSyncCandidateItemSchema.safeParse(
|
|
369
|
-
candidateItem({ relevanceReasons: Array(
|
|
388
|
+
candidateItem({ relevanceReasons: Array(16).fill(reason) }),
|
|
389
|
+
).success,
|
|
390
|
+
).toBe(true);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("refuses more than sixteen reasons", () => {
|
|
394
|
+
const reason = { kind: "evaluation", code: "explored_t1" };
|
|
395
|
+
expect(
|
|
396
|
+
SourceSyncCandidateItemSchema.safeParse(
|
|
397
|
+
candidateItem({ relevanceReasons: Array(17).fill(reason) }),
|
|
398
|
+
).success,
|
|
399
|
+
).toBe(false);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it("a typed item carries its resolved document type and confidence", () => {
|
|
403
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
404
|
+
candidateItem({
|
|
405
|
+
documentType: "corporate_formation_record",
|
|
406
|
+
documentTypeConfidence: 0.92,
|
|
407
|
+
sourceClass: "other",
|
|
408
|
+
}),
|
|
409
|
+
);
|
|
410
|
+
expect(parsed.success).toBe(true);
|
|
411
|
+
expect(parsed.data?.documentType).toBe("corporate_formation_record");
|
|
412
|
+
expect(parsed.data?.documentTypeConfidence).toBe(0.92);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("a v1-pinned classified item has no document type", () => {
|
|
416
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
417
|
+
candidateItem({ sourceClass: "board_minutes" }),
|
|
418
|
+
);
|
|
419
|
+
expect(parsed.success).toBe(true);
|
|
420
|
+
expect(parsed.data?.documentType).toBeNull();
|
|
421
|
+
expect(parsed.data?.documentTypeConfidence).toBeNull();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("an unclassified line has no document type", () => {
|
|
425
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
426
|
+
candidateItem({
|
|
427
|
+
visibility: "hidden",
|
|
428
|
+
classificationState: "unclassified",
|
|
429
|
+
recommendedUse: null,
|
|
430
|
+
sourceClass: null,
|
|
431
|
+
included: false,
|
|
432
|
+
documentType: "strategy_memo",
|
|
433
|
+
documentTypeConfidence: 0.9,
|
|
434
|
+
}),
|
|
435
|
+
);
|
|
436
|
+
expect(parsed.success).toBe(false);
|
|
437
|
+
expect(parsed.error?.issues[0]?.message).toBe(
|
|
438
|
+
"an unclassified line has no document type",
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("a known document type carries a confidence; unknown and absent carry none", () => {
|
|
443
|
+
for (const [documentType, documentTypeConfidence, ok] of [
|
|
444
|
+
["strategy_memo", 0.9, true],
|
|
445
|
+
["strategy_memo", null, false],
|
|
446
|
+
["unknown", null, true],
|
|
447
|
+
["unknown", 0.4, false],
|
|
448
|
+
[null, null, true],
|
|
449
|
+
[null, 0.4, false],
|
|
450
|
+
] as const) {
|
|
451
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
452
|
+
candidateItem({ documentType, documentTypeConfidence }),
|
|
453
|
+
);
|
|
454
|
+
expect(parsed.success).toBe(ok);
|
|
455
|
+
if (!ok) {
|
|
456
|
+
expect(parsed.error?.issues[0]?.message).toBe(
|
|
457
|
+
"a known document type carries a confidence; unknown and absent carry none",
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("refuses a document type outside the closed set", () => {
|
|
464
|
+
expect(
|
|
465
|
+
SourceSyncCandidateItemSchema.safeParse(
|
|
466
|
+
candidateItem({
|
|
467
|
+
documentType: "irrelevant_doc",
|
|
468
|
+
documentTypeConfidence: 0.9,
|
|
469
|
+
}),
|
|
370
470
|
).success,
|
|
371
471
|
).toBe(false);
|
|
372
472
|
});
|
|
373
473
|
|
|
474
|
+
it("the document type keys are required", () => {
|
|
475
|
+
for (const key of ["documentType", "documentTypeConfidence"]) {
|
|
476
|
+
const item: Record<string, unknown> = candidateItem();
|
|
477
|
+
delete item[key];
|
|
478
|
+
expect(SourceSyncCandidateItemSchema.safeParse(item).success).toBe(false);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
374
482
|
it("a reason whose code belongs to another kind is refused", () => {
|
|
375
483
|
for (const reason of [
|
|
376
484
|
{ kind: "lexicon", code: "version_churn", term: "board", count: 1 },
|
|
@@ -382,6 +490,37 @@ describe("candidate relevance", () => {
|
|
|
382
490
|
}
|
|
383
491
|
});
|
|
384
492
|
|
|
493
|
+
it("accepts the typed-relevance reason codes", () => {
|
|
494
|
+
const relevanceReasons = [
|
|
495
|
+
{ kind: "model", code: "type_policy", score: 0.05 },
|
|
496
|
+
{ kind: "model", code: "peek_strategy_evidence_present", score: 0.8 },
|
|
497
|
+
{ kind: "model", code: "peek_strategy_evidence_absent", score: 0.1 },
|
|
498
|
+
{ kind: "evaluation", code: "peek_insufficient_evidence" },
|
|
499
|
+
{ kind: "evaluation", code: "type_disagreement" },
|
|
500
|
+
{ kind: "evaluation", code: "type_below_confidence" },
|
|
501
|
+
{ kind: "evaluation", code: "execution_failed_t1" },
|
|
502
|
+
{ kind: "evaluation", code: "execution_failed_t2" },
|
|
503
|
+
];
|
|
504
|
+
expect(
|
|
505
|
+
SourceSyncCandidateItemSchema.safeParse(
|
|
506
|
+
candidateItem({ relevanceReasons }),
|
|
507
|
+
).success,
|
|
508
|
+
).toBe(true);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it("a typed-relevance code under the wrong kind is refused", () => {
|
|
512
|
+
for (const reason of [
|
|
513
|
+
{ kind: "evaluation", code: "type_policy" },
|
|
514
|
+
{ kind: "model", code: "type_disagreement", score: 0.5 },
|
|
515
|
+
]) {
|
|
516
|
+
expect(
|
|
517
|
+
SourceSyncCandidateItemSchema.safeParse(
|
|
518
|
+
candidateItem({ relevanceReasons: [reason] }),
|
|
519
|
+
).success,
|
|
520
|
+
).toBe(false);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
385
524
|
it("refuses a lexicon reason without a term or a count, and a model score outside 0..1", () => {
|
|
386
525
|
for (const reason of [
|
|
387
526
|
{ kind: "lexicon", code: "title_term", count: 1 },
|
|
@@ -16,12 +16,12 @@ import { DecisionEvidenceRowSchema } from "../../decisions/presentation";
|
|
|
16
16
|
import {
|
|
17
17
|
SourceSyncApplyResponseSchema,
|
|
18
18
|
SourceSyncApproveResponseSchema,
|
|
19
|
-
SourceSyncCandidateItemSchema,
|
|
20
19
|
SourceSyncCandidateSetResponseSchema,
|
|
21
20
|
SourceSyncDocChangeSchema,
|
|
22
21
|
SourceSyncScopeApprovalProjectionSchema,
|
|
23
22
|
SourceSyncSynthesisResponseSchema,
|
|
24
23
|
} from "../schemas";
|
|
24
|
+
import { SourceSyncCandidateItemSchema } from "../candidate-item";
|
|
25
25
|
|
|
26
26
|
const UNIT_SCOPE = "unit:3f1c2d4e-5a6b-4c7d-8e9f-a0b1c2d3e4f5";
|
|
27
27
|
|