@company-semantics/contracts 97.0.0 → 99.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.
@@ -1,58 +1,104 @@
1
1
  /**
2
2
  * Ingestion Surfaces
3
3
  *
4
- * Per-surface views of the single format registry. A surface names the
5
- * families it consumes; everything else — the formats, the file-input `accept`
6
- * string, the "Supported: …" copy, the transport byte ceiling — is DERIVED from
4
+ * Per-surface views of the single format registry. A surface declares a
5
+ * treatment per family it consumes plus a required byte ceiling; everything
6
+ * else — the families, the formats, the file-input `accept` string, the
7
+ * "Supported: …" copy, the transport byte ceiling — is DERIVED from
7
8
  * `INGESTION_FORMATS` through that declaration. No surface re-spells a MIME.
8
9
  *
9
10
  * Invariants:
10
11
  * - Every surface admits at least one registry format.
11
12
  * - `maxBytesFor` never exceeds the registry cap of any admitted format, and
12
- * never exceeds the surface ceiling when one is declared.
13
+ * never exceeds the surface ceiling.
13
14
  *
14
15
  * @see ADR-CONTRACTS-159 for design rationale
15
16
  */
16
17
 
17
18
  import { INGESTION_FORMATS } from "./registry";
18
19
  import type {
20
+ IngestionFamily,
19
21
  IngestionFormat,
20
22
  IngestionSurface,
21
23
  IngestionSurfaceKey,
24
+ Treatment,
22
25
  } from "./types";
23
26
 
24
- /** 10 MiB — the org-chart ceiling (see the surface JSDoc below). */
25
- const ORG_CHART_MAX_BYTES = 10 * 1024 * 1024;
27
+ const MIB = 1024 * 1024;
28
+
29
+ /** Canonical family order; `familiesOf` returns families in this order. */
30
+ const FAMILY_ORDER: readonly IngestionFamily[] = [
31
+ "text",
32
+ "document",
33
+ "image",
34
+ "diagram",
35
+ "audio",
36
+ "video",
37
+ ];
26
38
 
27
39
  /**
28
40
  * The canonical surface declarations.
29
41
  *
30
- * - `company-md`: context-bank and goals extraction. Consumes text and
31
- * documents; images, diagrams and media are not extracted on this surface
32
- * yet, so the picker must not advertise them.
33
- * - `org-chart`: the HRIS / org-chart import. Consumes text and documents plus
34
- * images and diagrams (a photographed or drawn chart goes to a vision-capable
35
- * model natively). The 10 MiB ceiling bounds the bytes handed to that model
36
- * per upload; the registry caps for its families would otherwise reach 50 MB.
42
+ * - `company-md`: context bank, goals extraction, Source Sync landing. Text and
43
+ * documents become text. (Image/diagram vision on this surface is a later,
44
+ * separate change: it needs its own extraction prompt and discovery eval.)
45
+ * - `org-chart`: HRIS / org-chart import. A photographed, drawn or PDF chart
46
+ * goes to a vision-capable model natively; 10 MiB bounds the bytes per upload.
47
+ * - `chat`: chat composer attachments. Text becomes text; a PDF and images go
48
+ * to the model natively. At most 8 files per turn. 5 MiB per file = the composer's per-file cap today;
49
+ * the request body ceiling (base64, several files) is derived from it by the
50
+ * chat PRD, not declared here. Audio/video are refused until a transcript
51
+ * path is wired.
52
+ * - `profile-image`: avatar and org-logo uploads. Images are stored, never
53
+ * extracted; 5 MiB matches the current avatar/logo cap.
37
54
  */
38
55
  export const INGESTION_SURFACES: Readonly<
39
56
  Record<IngestionSurfaceKey, IngestionSurface>
40
57
  > = {
41
58
  "company-md": {
42
59
  key: "company-md",
43
- families: ["text", "document"],
60
+ treatments: { text: "text", document: "text" },
61
+ maxBytes: 50_000_000,
44
62
  },
45
63
  "org-chart": {
46
64
  key: "org-chart",
47
- families: ["text", "document", "image", "diagram"],
48
- maxBytes: ORG_CHART_MAX_BYTES,
65
+ treatments: {
66
+ text: "text",
67
+ document: "vision",
68
+ image: "vision",
69
+ diagram: "vision",
70
+ },
71
+ maxBytes: 10 * MIB,
72
+ },
73
+ chat: {
74
+ key: "chat",
75
+ treatments: { text: "text", document: "vision", image: "vision" },
76
+ maxBytes: 5 * MIB,
77
+ // The composer sets no maxFiles today (unbounded; only the 10 MiB body cap
78
+ // limits a turn). 8 bounds per-turn vision/billing cost and the derived body
79
+ // ceiling (8 × 5 MiB × 4/3 base64 + envelope, computed by the chat PRD) while
80
+ // covering the multi-screenshot case.
81
+ maxFilesPerTurn: 8,
82
+ },
83
+ "profile-image": {
84
+ key: "profile-image",
85
+ treatments: { image: "store" },
86
+ maxBytes: 5 * MIB,
49
87
  },
50
88
  };
51
89
 
52
- /** The registry formats a surface admits, in registry order. */
53
- export function formatsFor(surface: IngestionSurfaceKey): IngestionFormat[] {
54
- const { families } = INGESTION_SURFACES[surface];
55
- return INGESTION_FORMATS.filter((entry) => families.includes(entry.family));
90
+ /** The families a surface admits, in canonical family order. */
91
+ export function familiesOf(surface: IngestionSurfaceKey): IngestionFamily[] {
92
+ const { treatments } = INGESTION_SURFACES[surface];
93
+ return FAMILY_ORDER.filter((family) => treatments[family] !== undefined);
94
+ }
95
+
96
+ /** What the surface does with this format; null = the surface refuses it. */
97
+ export function treatmentFor(
98
+ surface: IngestionSurfaceKey,
99
+ format: IngestionFormat,
100
+ ): Treatment | null {
101
+ return INGESTION_SURFACES[surface].treatments[format.family] ?? null;
56
102
  }
57
103
 
58
104
  /** True when the surface admits this registry format. */
@@ -60,13 +106,22 @@ export function surfaceAdmits(
60
106
  surface: IngestionSurfaceKey,
61
107
  format: IngestionFormat,
62
108
  ): boolean {
63
- return INGESTION_SURFACES[surface].families.includes(format.family);
109
+ return treatmentFor(surface, format) !== null;
64
110
  }
65
111
 
66
- /** Comma-joined extension string for the surface's file input `accept`. */
112
+ /** The registry formats a surface admits, in registry order. */
113
+ export function formatsFor(surface: IngestionSurfaceKey): IngestionFormat[] {
114
+ return INGESTION_FORMATS.filter((entry) => surfaceAdmits(surface, entry));
115
+ }
116
+
117
+ /**
118
+ * The `<input accept>` value for a surface: per format, its extensions then its
119
+ * MIME (".png,image/png,.jpg,.jpeg,image/jpeg,…"). Both forms are valid in
120
+ * `accept`, and a MIME-only matcher (the chat composer) can use the same string.
121
+ */
67
122
  export function acceptFor(surface: IngestionSurfaceKey): string {
68
123
  return formatsFor(surface)
69
- .flatMap((entry) => [...entry.extensions])
124
+ .flatMap((entry) => [...entry.extensions, entry.mime])
70
125
  .join(",");
71
126
  }
72
127
 
@@ -77,15 +132,13 @@ export function supportedLabelFor(surface: IngestionSurfaceKey): string {
77
132
  }
78
133
 
79
134
  /**
80
- * The transport byte ceiling for a surface: the largest registry cap among
81
- * its admitted formats, tightened to the surface ceiling when one is declared.
82
- * The per-format registry cap still applies underneath it.
135
+ * The transport byte ceiling: the largest registry cap among admitted formats,
136
+ * tightened to the (required) surface ceiling.
83
137
  */
84
138
  export function maxBytesFor(surface: IngestionSurfaceKey): number {
85
139
  const registryMax = formatsFor(surface).reduce(
86
140
  (max, entry) => Math.max(max, entry.maxBytes),
87
141
  0,
88
142
  );
89
- const ceiling = INGESTION_SURFACES[surface].maxBytes;
90
- return ceiling === undefined ? registryMax : Math.min(registryMax, ceiling);
143
+ return Math.min(registryMax, INGESTION_SURFACES[surface].maxBytes);
91
144
  }
@@ -7,7 +7,7 @@
7
7
  * private dispatch fields locally) and the app upload UI (which derives its
8
8
  * `accept` attribute and supported-formats copy from this).
9
9
  *
10
- * Types only — no runtime behavior. The backend-only normalizer routing
10
+ * Types plus the `FORMAT_CLASSES` and `TREATMENTS` vocabulary constants. The backend-only normalizer routing
11
11
  * (`canonicalKind`, `normalizerKey`) deliberately does NOT live here; it is an
12
12
  * implementation detail of the backend dispatch and would pollute the shared
13
13
  * vocabulary.
@@ -17,7 +17,7 @@
17
17
 
18
18
  /**
19
19
  * The semantic family a format belongs to. A SURFACE (an upload picker and the
20
- * route behind it) declares which families it consumes; the accept-set and the
20
+ * route behind it) declares a treatment per family it consumes; the accept-set and the
21
21
  * byte ceiling for that surface derive from the registry through this field.
22
22
  * Public and semantic — NOT the backend normalizer dispatch (`canonicalKind` /
23
23
  * `normalizerKey` stay backend-local, ADR-CONTRACTS-075).
@@ -27,6 +27,29 @@
27
27
  export type IngestionFamily =
28
28
  "text" | "document" | "image" | "diagram" | "audio" | "video";
29
29
 
30
+ /**
31
+ * The coarse, provider-agnostic kind of a format, used wherever the system
32
+ * classes a file without reading it: the Source Sync inventory `mime_class`,
33
+ * the discovery topology mix, probe groups, and the spreadsheet `source_kind`.
34
+ * Declared ON the format entry — no consumer keeps an extension → class table.
35
+ * Canonical order (the inventory CHECK and every derived list follow it).
36
+ */
37
+ export const FORMAT_CLASSES = [
38
+ "docs",
39
+ "slides",
40
+ "sheets",
41
+ "pdf",
42
+ "text",
43
+ "image",
44
+ "diagram",
45
+ "audio",
46
+ "video",
47
+ ] as const;
48
+ export type FormatClass = (typeof FORMAT_CLASSES)[number];
49
+
50
+ /** A Source Sync inventory class: a format class, or a provider shortcut row. */
51
+ export type InventoryClass = FormatClass | "shortcut";
52
+
30
53
  /**
31
54
  * One accepted upload format, keyed by its canonical MIME type.
32
55
  *
@@ -35,14 +58,17 @@ export type IngestionFamily =
35
58
  * - `extensions` are lowercase, dot-prefixed (e.g. `.docx`).
36
59
  * - `label` is the human display GROUP and is intentionally shared across
37
60
  * related formats (`.docx` + `.doc` are both `Word`; the rasters are all
38
- * `Image`; `.svg` + `.drawio` are `Diagram`).
61
+ * `Image`; `.svg` is `Diagram`).
39
62
  * - `family` is the semantic family a surface selects on; every entry has one.
63
+ * - `class` is the coarse class (one of `FORMAT_CLASSES`); every entry has one.
40
64
  */
41
65
  export interface IngestionFormat {
42
66
  /** Canonical MIME type (the registry key). */
43
67
  mime: string;
44
- /** Semantic family the format belongs to (what a surface selects on). */
68
+ /** Semantic family the format belongs to (what a surface declares a treatment for). */
45
69
  family: IngestionFamily;
70
+ /** Coarse class for classing without reading (inventory, topology, probes). */
71
+ class: FormatClass;
46
72
  /** Lowercase, dot-prefixed filename extensions for this format. */
47
73
  extensions: readonly string[];
48
74
  /** Human display group label (e.g. `Word`, `Image`, `Audio`). */
@@ -51,23 +77,62 @@ export interface IngestionFormat {
51
77
  maxBytes: number;
52
78
  }
53
79
 
54
- /** The upload surfaces that consume the registry. */
55
- export type IngestionSurfaceKey = "company-md" | "org-chart";
56
-
57
80
  /**
58
- * What one upload surface consumes: the families it admits and, optionally, a
59
- * byte ceiling tighter than the registry caps of those families.
81
+ * What a surface does with an admitted family. The backend proves at load that
82
+ * it can perform every declared (surface, family, treatment) for every admitted
83
+ * format, or it refuses to start.
60
84
  *
61
- * @see ADR-CONTRACTS-159
85
+ * - `text`: normalize to text only (a PDF is text-extracted).
86
+ * - `vision`: hand the model the richest native form — images as image parts,
87
+ * a PDF as a binary document, text-native formats (docx, csv, …) as text.
88
+ * - `transcript`: audio/video become a transcript.
89
+ * - `store`: bytes are kept as-is (after format-preserving preparation such as
90
+ * HEIC → JPEG); nothing is extracted.
91
+ */
92
+ export const TREATMENTS = ["text", "vision", "transcript", "store"] as const;
93
+ export type Treatment = (typeof TREATMENTS)[number];
94
+
95
+ /** The surfaces that consume the registry. */
96
+ export type IngestionSurfaceKey =
97
+ "company-md" | "org-chart" | "chat" | "profile-image";
98
+
99
+ /**
100
+ * One surface: the treatment it applies per admitted family, and its byte
101
+ * ceiling. `families` is derived (`familiesOf`), never declared.
62
102
  */
63
103
  export interface IngestionSurface {
64
104
  /** The surface this declaration describes. */
65
105
  key: IngestionSurfaceKey;
66
- /** Families the surface admits; a format outside them is refused (415). */
67
- families: readonly IngestionFamily[];
106
+ /** Admitted family → what the surface does with it. Absent family = refused (415). */
107
+ treatments: Readonly<Partial<Record<IngestionFamily, Treatment>>>;
108
+ /** Surface byte ceiling (required). Caps every admitted format below its registry `maxBytes`. */
109
+ maxBytes: number;
68
110
  /**
69
- * Surface-level byte ceiling. When set it caps every admitted format below
70
- * its registry `maxBytes`; absent means the registry caps apply as-is.
111
+ * Most files one turn may carry. Declared only on `chat` (required there,
112
+ * pinned by test); upload surfaces take one file per request and omit it.
71
113
  */
72
- maxBytes?: number;
114
+ maxFilesPerTurn?: number;
115
+ }
116
+
117
+ /** External sources that hand the pipeline provider-native files. */
118
+ export type IngestionProviderKey = "google-drive";
119
+
120
+ /** A provider-native type that is exported to a registry format before ingestion. */
121
+ export interface ProviderNativeFormat {
122
+ /** The provider's own MIME (e.g. a Google Workspace type). */
123
+ mime: string;
124
+ /** Human label for the native type (e.g. `Google Doc`). */
125
+ label: string;
126
+ /** Registry MIME the provider exports this type to. Must resolve via `byMime`. */
127
+ exportsTo: string;
128
+ }
129
+
130
+ /** One provider: its native types and the structural container MIMEs it lists. */
131
+ export interface IngestionProvider {
132
+ /** The provider this declaration describes. */
133
+ key: IngestionProviderKey;
134
+ /** Native types the provider exports to registry formats. */
135
+ nativeFormats: readonly ProviderNativeFormat[];
136
+ /** Structural (non-file) MIMEs the provider lists: folders and shortcuts. */
137
+ containerMimes: { readonly folder: string; readonly shortcut: string };
73
138
  }