@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "79.1.0",
3
+ "version": "80.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = 'c6eea14298b3' as const;
3
- export const SPEC_HASH_FULL = 'c6eea14298b3f6282e821b61fac860d7c18fbb8af4b5ecf660bc09665a215d9b' as const;
2
+ export const SPEC_HASH = 'c2954b6d06a9' as const;
3
+ export const SPEC_HASH_FULL = 'c2954b6d06a981632d4d61991aa61f16ee28485116ec66949fe08d64cbde4ceb' as const;
@@ -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
- unresolvedTreatmentCount: number;
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 and
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
- unresolvedTreatmentCount: 0,
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
+ }
@@ -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
- unresolvedTreatmentCount: z.number().int().min(0),
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
  >;