@company-semantics/contracts 79.1.0 → 80.0.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 +156 -1
- package/src/generated/openapi-routes.ts +2 -0
- package/src/source-sync/README.md +6 -3
- package/src/source-sync/__tests__/schemas.test.ts +16 -1
- package/src/source-sync/candidate-totals.ts +42 -0
- package/src/source-sync/index.ts +5 -0
- package/src/source-sync/schemas.ts +25 -1
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 = 'c2954b6d06a9' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = 'c2954b6d06a981632d4d61991aa61f16ee28485116ec66949fe08d64cbde4ceb' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -1666,6 +1666,46 @@ export interface paths {
|
|
|
1666
1666
|
patch?: never;
|
|
1667
1667
|
trace?: never;
|
|
1668
1668
|
};
|
|
1669
|
+
"/api/analysis-workflows/{id}": {
|
|
1670
|
+
parameters: {
|
|
1671
|
+
query?: never;
|
|
1672
|
+
header?: never;
|
|
1673
|
+
path?: never;
|
|
1674
|
+
cookie?: never;
|
|
1675
|
+
};
|
|
1676
|
+
/**
|
|
1677
|
+
* Read one analysis workflow’s generic lifecycle envelope
|
|
1678
|
+
* @description The domain-agnostic projection a client polls: identity, registered type, activity-free lifecycle state, start and completion times, the opaque domain progress and result payloads (null until the domain adapter supplies them), a closed failure class on a failed workflow, and the capabilities the surface may offer — canStop from the generic layer, canContinue as the domain declares it. Org-scoped: a workflow in another workspace answers 404.
|
|
1679
|
+
*/
|
|
1680
|
+
get: operations["getAnalysisWorkflow"];
|
|
1681
|
+
put?: never;
|
|
1682
|
+
post?: never;
|
|
1683
|
+
delete?: never;
|
|
1684
|
+
options?: never;
|
|
1685
|
+
head?: never;
|
|
1686
|
+
patch?: never;
|
|
1687
|
+
trace?: never;
|
|
1688
|
+
};
|
|
1689
|
+
"/api/analysis-workflows/{id}/stop": {
|
|
1690
|
+
parameters: {
|
|
1691
|
+
query?: never;
|
|
1692
|
+
header?: never;
|
|
1693
|
+
path?: never;
|
|
1694
|
+
cookie?: never;
|
|
1695
|
+
};
|
|
1696
|
+
get?: never;
|
|
1697
|
+
put?: never;
|
|
1698
|
+
/**
|
|
1699
|
+
* Request that an analysis workflow stop at its next safe boundary
|
|
1700
|
+
* @description The one generic COMMAND. Records a cooperative stop request and moves a pending or running workflow to draining; nothing is aborted — the worker halts at a boundary the domain chooses. Answers 202 (acknowledged, not landed) when the request was accepted, and 200 with { stopped: false, reason: "not_running" } when the workflow is already draining or already settled, because a retry against a stopped workflow is a success. Runtime posture: intent write, impersonation allow (a stop only reduces spend). Org-scoped: a workflow in another workspace answers 404.
|
|
1701
|
+
*/
|
|
1702
|
+
post: operations["stopAnalysisWorkflow"];
|
|
1703
|
+
delete?: never;
|
|
1704
|
+
options?: never;
|
|
1705
|
+
head?: never;
|
|
1706
|
+
patch?: never;
|
|
1707
|
+
trace?: never;
|
|
1708
|
+
};
|
|
1669
1709
|
"/api/company-md/tree": {
|
|
1670
1710
|
parameters: {
|
|
1671
1711
|
query?: never;
|
|
@@ -6446,6 +6486,35 @@ export interface components {
|
|
|
6446
6486
|
fingerprint: string;
|
|
6447
6487
|
note?: string;
|
|
6448
6488
|
};
|
|
6489
|
+
AnalysisWorkflowProjectionResponse: {
|
|
6490
|
+
/** Format: uuid */
|
|
6491
|
+
id: string;
|
|
6492
|
+
type: string;
|
|
6493
|
+
/** @enum {string} */
|
|
6494
|
+
state: "pending" | "running" | "draining" | "stopped" | "complete" | "failed";
|
|
6495
|
+
startedAt: string | null;
|
|
6496
|
+
completedAt: string | null;
|
|
6497
|
+
progress: unknown | null;
|
|
6498
|
+
result: unknown | null;
|
|
6499
|
+
error?: string;
|
|
6500
|
+
capabilities: {
|
|
6501
|
+
canStop: boolean;
|
|
6502
|
+
canContinue: boolean;
|
|
6503
|
+
};
|
|
6504
|
+
};
|
|
6505
|
+
StopAnalysisWorkflowResponse: {
|
|
6506
|
+
/** @constant */
|
|
6507
|
+
stopped: true;
|
|
6508
|
+
/** Format: uuid */
|
|
6509
|
+
workflowId: string;
|
|
6510
|
+
/** @constant */
|
|
6511
|
+
state: "draining";
|
|
6512
|
+
} | {
|
|
6513
|
+
/** @constant */
|
|
6514
|
+
stopped: false;
|
|
6515
|
+
/** @constant */
|
|
6516
|
+
reason: "not_running";
|
|
6517
|
+
};
|
|
6449
6518
|
CompanyMdListResponse: {
|
|
6450
6519
|
items: {
|
|
6451
6520
|
id: string;
|
|
@@ -8584,6 +8653,7 @@ export interface components {
|
|
|
8584
8653
|
/** @enum {string} */
|
|
8585
8654
|
recommendedUse: "attached" | "direction_only" | "excluded";
|
|
8586
8655
|
use: ("attached" | "direction_only" | "excluded") | null;
|
|
8656
|
+
included: boolean;
|
|
8587
8657
|
treatmentUnresolved: boolean;
|
|
8588
8658
|
matchReason: string;
|
|
8589
8659
|
unreadableReason: ("unsupported_type" | "needs_ocr" | "unsupported_kind") | null;
|
|
@@ -8621,7 +8691,11 @@ export interface components {
|
|
|
8621
8691
|
};
|
|
8622
8692
|
}[];
|
|
8623
8693
|
withheldQuestionCount: number;
|
|
8624
|
-
|
|
8694
|
+
totals: {
|
|
8695
|
+
files: number;
|
|
8696
|
+
filesIncluded: number;
|
|
8697
|
+
filesNeedingChoice: number;
|
|
8698
|
+
};
|
|
8625
8699
|
scopes: {
|
|
8626
8700
|
scopeKey: string;
|
|
8627
8701
|
/** @enum {string} */
|
|
@@ -12121,6 +12195,87 @@ export interface operations {
|
|
|
12121
12195
|
};
|
|
12122
12196
|
};
|
|
12123
12197
|
};
|
|
12198
|
+
getAnalysisWorkflow: {
|
|
12199
|
+
parameters: {
|
|
12200
|
+
query?: never;
|
|
12201
|
+
header?: never;
|
|
12202
|
+
path: {
|
|
12203
|
+
id: string;
|
|
12204
|
+
};
|
|
12205
|
+
cookie?: never;
|
|
12206
|
+
};
|
|
12207
|
+
requestBody?: never;
|
|
12208
|
+
responses: {
|
|
12209
|
+
/** @description The workflow’s lifecycle envelope with its capabilities */
|
|
12210
|
+
200: {
|
|
12211
|
+
headers: {
|
|
12212
|
+
[name: string]: unknown;
|
|
12213
|
+
};
|
|
12214
|
+
content: {
|
|
12215
|
+
"application/json": components["schemas"]["AnalysisWorkflowProjectionResponse"];
|
|
12216
|
+
};
|
|
12217
|
+
};
|
|
12218
|
+
/** @description The workflow id is not a uuid */
|
|
12219
|
+
400: {
|
|
12220
|
+
headers: {
|
|
12221
|
+
[name: string]: unknown;
|
|
12222
|
+
};
|
|
12223
|
+
content?: never;
|
|
12224
|
+
};
|
|
12225
|
+
/** @description No such workflow in this workspace */
|
|
12226
|
+
404: {
|
|
12227
|
+
headers: {
|
|
12228
|
+
[name: string]: unknown;
|
|
12229
|
+
};
|
|
12230
|
+
content?: never;
|
|
12231
|
+
};
|
|
12232
|
+
};
|
|
12233
|
+
};
|
|
12234
|
+
stopAnalysisWorkflow: {
|
|
12235
|
+
parameters: {
|
|
12236
|
+
query?: never;
|
|
12237
|
+
header?: never;
|
|
12238
|
+
path: {
|
|
12239
|
+
id: string;
|
|
12240
|
+
};
|
|
12241
|
+
cookie?: never;
|
|
12242
|
+
};
|
|
12243
|
+
requestBody?: never;
|
|
12244
|
+
responses: {
|
|
12245
|
+
/** @description Nothing to stop: the workflow is not running */
|
|
12246
|
+
200: {
|
|
12247
|
+
headers: {
|
|
12248
|
+
[name: string]: unknown;
|
|
12249
|
+
};
|
|
12250
|
+
content: {
|
|
12251
|
+
"application/json": components["schemas"]["StopAnalysisWorkflowResponse"];
|
|
12252
|
+
};
|
|
12253
|
+
};
|
|
12254
|
+
/** @description Stop requested; the workflow is draining */
|
|
12255
|
+
202: {
|
|
12256
|
+
headers: {
|
|
12257
|
+
[name: string]: unknown;
|
|
12258
|
+
};
|
|
12259
|
+
content: {
|
|
12260
|
+
"application/json": components["schemas"]["StopAnalysisWorkflowResponse"];
|
|
12261
|
+
};
|
|
12262
|
+
};
|
|
12263
|
+
/** @description The workflow id is not a uuid */
|
|
12264
|
+
400: {
|
|
12265
|
+
headers: {
|
|
12266
|
+
[name: string]: unknown;
|
|
12267
|
+
};
|
|
12268
|
+
content?: never;
|
|
12269
|
+
};
|
|
12270
|
+
/** @description No such workflow in this workspace */
|
|
12271
|
+
404: {
|
|
12272
|
+
headers: {
|
|
12273
|
+
[name: string]: unknown;
|
|
12274
|
+
};
|
|
12275
|
+
content?: never;
|
|
12276
|
+
};
|
|
12277
|
+
};
|
|
12278
|
+
};
|
|
12124
12279
|
getCompanyMdTree: {
|
|
12125
12280
|
parameters: {
|
|
12126
12281
|
query?: {
|
|
@@ -9,6 +9,8 @@ export const openApiRoutes = {
|
|
|
9
9
|
'/api/account/deletion-eligibility': ['GET'],
|
|
10
10
|
'/api/account/sessions': ['GET'],
|
|
11
11
|
'/api/account/sessions/{sessionId}': ['DELETE'],
|
|
12
|
+
'/api/analysis-workflows/{id}': ['GET'],
|
|
13
|
+
'/api/analysis-workflows/{id}/stop': ['POST'],
|
|
12
14
|
'/api/auth/consent/grant': ['POST'],
|
|
13
15
|
'/api/billing/price-list': ['GET'],
|
|
14
16
|
'/api/capabilities': ['GET'],
|
|
@@ -127,10 +127,13 @@ way. This directory holds that shared spelling.
|
|
|
127
127
|
- `SourceSyncDiscoveryRunResponseSchema` / `SourceSyncDiscoveryRunResponse` —
|
|
128
128
|
the discovery run started, and the workflow narrating it.
|
|
129
129
|
- `SourceSyncCandidateItemSchema` / `SourceSyncCandidateItem` — one source
|
|
130
|
-
proposed for one target document
|
|
130
|
+
proposed for one target document, with the server's `included`.
|
|
131
131
|
- `SourceSyncCandidateSetResponseSchema` / `SourceSyncCandidateSetResponse` —
|
|
132
|
-
the draft set under review: its revision, items, presented questions
|
|
133
|
-
cost estimate.
|
|
132
|
+
the draft set under review: its revision, items, presented questions,
|
|
133
|
+
distinct-file `totals` and cost estimate.
|
|
134
|
+
- `SourceSyncCandidateTotalsSchema` / `SourceSyncCandidateTotals` and
|
|
135
|
+
`candidateTotalsOf(items)` — THE file count: distinct `sourceId`, never lines
|
|
136
|
+
(ADR-CONTRACTS-195).
|
|
134
137
|
- `SourceSyncCandidateItemPatchSchema` / `SourceSyncCandidateItemPatch` — one
|
|
135
138
|
edit to one candidate item (use, class or target); never empty.
|
|
136
139
|
- `SourceSyncAddCandidateRequestSchema` / `SourceSyncAddCandidateRequest` —
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { describe, expect, it } from "vitest";
|
|
17
17
|
import {
|
|
18
|
+
candidateTotalsOf,
|
|
18
19
|
SourceSyncCandidateItemSchema,
|
|
19
20
|
SourceSyncCandidateSetResponseSchema,
|
|
20
21
|
SourceSyncDiscoveryRunResponseSchema,
|
|
@@ -50,6 +51,7 @@ function candidateItem(overrides: Record<string, unknown> = {}) {
|
|
|
50
51
|
audienceFit: "fits",
|
|
51
52
|
recommendedUse: "direction_only",
|
|
52
53
|
use: null,
|
|
54
|
+
included: true,
|
|
53
55
|
treatmentUnresolved: false,
|
|
54
56
|
matchReason: "A board deck for the current quarter.",
|
|
55
57
|
unreadableReason: null,
|
|
@@ -96,7 +98,7 @@ function candidateSet(overrides: Record<string, unknown> = {}) {
|
|
|
96
98
|
items: [candidateItem()],
|
|
97
99
|
questions: [presentedQuestion()],
|
|
98
100
|
withheldQuestionCount: 0,
|
|
99
|
-
|
|
101
|
+
totals: { files: 1, filesIncluded: 1, filesNeedingChoice: 0 },
|
|
100
102
|
scopes: [],
|
|
101
103
|
estimate: {
|
|
102
104
|
lowMicroUsd: "250000",
|
|
@@ -279,3 +281,16 @@ describe("SourceSyncDiscoveryRunResponseSchema", () => {
|
|
|
279
281
|
).toBe(true);
|
|
280
282
|
});
|
|
281
283
|
});
|
|
284
|
+
|
|
285
|
+
describe("candidateTotalsOf", () => {
|
|
286
|
+
it("counts distinct files, never lines", () => {
|
|
287
|
+
expect(
|
|
288
|
+
candidateTotalsOf([
|
|
289
|
+
{ sourceId: "a", included: true, treatmentUnresolved: true },
|
|
290
|
+
{ sourceId: "a", included: true, treatmentUnresolved: false },
|
|
291
|
+
{ sourceId: "b", included: false, treatmentUnresolved: true },
|
|
292
|
+
{ sourceId: "c", included: true, treatmentUnresolved: false },
|
|
293
|
+
]),
|
|
294
|
+
).toEqual({ files: 3, filesIncluded: 2, filesNeedingChoice: 1 });
|
|
295
|
+
});
|
|
296
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ONE FILE COUNT for a candidate review: distinct `sourceId`, never lines.
|
|
3
|
+
*
|
|
4
|
+
* A file routed to two company.md documents is two lines and ONE file. The
|
|
5
|
+
* server computes `SourceSyncCandidateSetResponse.totals` with this over every
|
|
6
|
+
* line the reader may see; a client that overlays an optimistic edit recomputes
|
|
7
|
+
* with the same function, so no surface counts "files" its own way.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
SourceSyncCandidateItem,
|
|
12
|
+
SourceSyncCandidateTotals,
|
|
13
|
+
} from "./schemas";
|
|
14
|
+
|
|
15
|
+
/** The item facts the count reads. */
|
|
16
|
+
export type CandidateTotalsInput = Pick<
|
|
17
|
+
SourceSyncCandidateItem,
|
|
18
|
+
"sourceId" | "included" | "treatmentUnresolved"
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Included when any of the file's lines is; in need of a choice when any
|
|
23
|
+
* included line's treatment is unresolved.
|
|
24
|
+
*/
|
|
25
|
+
export function candidateTotalsOf(
|
|
26
|
+
items: readonly CandidateTotalsInput[],
|
|
27
|
+
): SourceSyncCandidateTotals {
|
|
28
|
+
const files = new Set<string>();
|
|
29
|
+
const included = new Set<string>();
|
|
30
|
+
const needingChoice = new Set<string>();
|
|
31
|
+
for (const item of items) {
|
|
32
|
+
files.add(item.sourceId);
|
|
33
|
+
if (!item.included) continue;
|
|
34
|
+
included.add(item.sourceId);
|
|
35
|
+
if (item.treatmentUnresolved) needingChoice.add(item.sourceId);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
files: files.size,
|
|
39
|
+
filesIncluded: included.size,
|
|
40
|
+
filesNeedingChoice: needingChoice.size,
|
|
41
|
+
};
|
|
42
|
+
}
|
package/src/source-sync/index.ts
CHANGED
|
@@ -48,6 +48,7 @@ export type {
|
|
|
48
48
|
SourceSyncDiscoveryRunResponse,
|
|
49
49
|
SourceSyncCandidateItem,
|
|
50
50
|
SourceSyncCandidateSetResponse,
|
|
51
|
+
SourceSyncCandidateTotals,
|
|
51
52
|
SourceSyncCandidateItemPatch,
|
|
52
53
|
SourceSyncAddCandidateRequest,
|
|
53
54
|
SourceSyncApproveRequest,
|
|
@@ -69,6 +70,7 @@ export {
|
|
|
69
70
|
SourceSyncDiscoveryRunResponseSchema,
|
|
70
71
|
SourceSyncCandidateItemSchema,
|
|
71
72
|
SourceSyncCandidateSetResponseSchema,
|
|
73
|
+
SourceSyncCandidateTotalsSchema,
|
|
72
74
|
SourceSyncCandidateItemPatchSchema,
|
|
73
75
|
SourceSyncAddCandidateRequestSchema,
|
|
74
76
|
SourceSyncApproveRequestSchema,
|
|
@@ -139,3 +141,6 @@ export {
|
|
|
139
141
|
SourceSyncReachUpgradeRequestSchema,
|
|
140
142
|
SourceSyncReachUpgradeResponseSchema,
|
|
141
143
|
} from "./reach";
|
|
144
|
+
|
|
145
|
+
export type { CandidateTotalsInput } from "./candidate-totals";
|
|
146
|
+
export { candidateTotalsOf } from "./candidate-totals";
|
|
@@ -118,6 +118,12 @@ export const SourceSyncCandidateItemSchema = z.object({
|
|
|
118
118
|
recommendedUse: z.enum(SOURCE_USES),
|
|
119
119
|
/** What the person has chosen; null until they (or an accepted default) decide. */
|
|
120
120
|
use: z.enum(SOURCE_USES).nullable(),
|
|
121
|
+
/**
|
|
122
|
+
* Whether this line is in the set that approval would land — the SERVER's
|
|
123
|
+
* answer, never derived from `use`. A decoy, a series' history or a line a
|
|
124
|
+
* person left out is false while its `use` is still null.
|
|
125
|
+
*/
|
|
126
|
+
included: z.boolean(),
|
|
121
127
|
/** True while a `widens` item — or one whose fit is not computed yet — has no person-chosen use: approval is refused until none remain. */
|
|
122
128
|
treatmentUnresolved: z.boolean(),
|
|
123
129
|
/** Product-owned copy saying why this file was proposed. Never model-authored prose. */
|
|
@@ -128,6 +134,16 @@ export const SourceSyncCandidateItemSchema = z.object({
|
|
|
128
134
|
.nullable(),
|
|
129
135
|
});
|
|
130
136
|
|
|
137
|
+
/** Distinct-file totals over every line of a candidate set the caller may see. */
|
|
138
|
+
export const SourceSyncCandidateTotalsSchema = z.object({
|
|
139
|
+
/** Files in the review: at least one line, included or left out. */
|
|
140
|
+
files: z.number().int().min(0),
|
|
141
|
+
/** Files with at least one included line. */
|
|
142
|
+
filesIncluded: z.number().int().min(0),
|
|
143
|
+
/** Files with at least one included line whose treatment is unresolved. */
|
|
144
|
+
filesNeedingChoice: z.number().int().min(0),
|
|
145
|
+
});
|
|
146
|
+
|
|
131
147
|
/** The draft set under review. `revision` moves on every edit and is echoed on approve. */
|
|
132
148
|
export const SourceSyncCandidateSetResponseSchema = z.object({
|
|
133
149
|
setId: z.string().uuid(),
|
|
@@ -138,7 +154,12 @@ export const SourceSyncCandidateSetResponseSchema = z.object({
|
|
|
138
154
|
/** The low-confidence routing, class and treatment questions, as presented. */
|
|
139
155
|
questions: z.array(DecisionQuestionPresentationSchema).max(50),
|
|
140
156
|
withheldQuestionCount: z.number().int().min(0),
|
|
141
|
-
|
|
157
|
+
/**
|
|
158
|
+
* The review's top-level totals, counted by DISTINCT FILE (`sourceId`) — never
|
|
159
|
+
* by line. A file routed to two documents is two lines and one file. Every
|
|
160
|
+
* surface reads these rather than counting `items`, which may be capped.
|
|
161
|
+
*/
|
|
162
|
+
totals: SourceSyncCandidateTotalsSchema,
|
|
142
163
|
/** Every scope of the set the caller may see, and where its approval stands. */
|
|
143
164
|
scopes: z.array(SourceSyncScopeApprovalProjectionSchema).max(500),
|
|
144
165
|
/** A RANGE, and the cap that is the promise. Null until the estimator has run. */
|
|
@@ -378,6 +399,9 @@ export type SourceSyncCandidateItem = z.infer<
|
|
|
378
399
|
export type SourceSyncCandidateSetResponse = z.infer<
|
|
379
400
|
typeof SourceSyncCandidateSetResponseSchema
|
|
380
401
|
>;
|
|
402
|
+
export type SourceSyncCandidateTotals = z.infer<
|
|
403
|
+
typeof SourceSyncCandidateTotalsSchema
|
|
404
|
+
>;
|
|
381
405
|
export type SourceSyncCandidateItemPatch = z.infer<
|
|
382
406
|
typeof SourceSyncCandidateItemPatchSchema
|
|
383
407
|
>;
|