@company-semantics/contracts 62.8.0 → 62.10.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": "62.8.0",
3
+ "version": "62.10.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,18 +19,31 @@ attribute and supported-formats copy from the same data.
19
19
  hand-maintained — adding a format updates both automatically.
20
20
  - No normalizer-routing fields here (`canonicalKind`, `normalizerKey` are
21
21
  backend-only). This package stays pure vocabulary.
22
+ - Every entry carries a `family`; `INGESTION_SURFACES` declares which families
23
+ each upload surface admits, and `formatsFor` / `acceptFor` /
24
+ `supportedLabelFor` / `maxBytesFor` are **derived** from that declaration
25
+ (ADR-CONTRACTS-159). A surface never re-spells a MIME.
22
26
 
23
27
  <!-- BEGIN GENERATED: readme-public-api — derived from code by `pnpm readme-api`. Do not edit. -->
24
28
 
25
29
  ## Public API
26
30
 
27
31
  - `INGESTION_FORMATS` — The canonical public format registry.
32
+ - `INGESTION_SURFACES` — The canonical surface declarations. - `company-md`: context-bank and goals extraction.
28
33
  - `INGEST_ACCEPT` — Comma-joined extension string for a file input's `accept` attribute.
34
+ - `IngestionFamily` _(type)_ — The semantic family a format belongs to.
29
35
  - `IngestionFormat` _(type)_ — One accepted upload format, keyed by its canonical MIME type.
36
+ - `IngestionSurface` _(type)_ — What one upload surface consumes: the families it admits and, optionally, a byte ceiling tighter than the…
37
+ - `IngestionSurfaceKey` _(type)_ — The upload surfaces that consume the registry.
30
38
  - `SUPPORTED_LABEL` — Human-readable supported-formats summary, derived from {@link humanLabels}.
31
39
  - `acceptExtensions` — All accepted filename extensions, in registry order.
40
+ - `acceptFor` — Comma-joined extension string for the surface's file input `accept`.
32
41
  - `byMime` — Look up the entry for a canonical MIME; undefined means unsupported.
42
+ - `formatsFor` — The registry formats a surface admits, in registry order.
33
43
  - `humanLabels` — Unique group labels for human display, in registry order.
44
+ - `maxBytesFor` — The transport byte ceiling for a surface: the largest registry cap among its admitted formats, tightened to…
45
+ - `supportedLabelFor` — "Supported: …" copy naming every label group the surface admits.
46
+ - `surfaceAdmits` — True when the surface admits this registry format.
34
47
 
35
48
  <!-- END GENERATED: readme-public-api -->
36
49
 
@@ -31,14 +31,14 @@ describe("ingestion registry", () => {
31
31
  it("derives INGEST_ACCEPT from every extension in registry order", () => {
32
32
  expect(INGEST_ACCEPT).toBe(acceptExtensions().join(","));
33
33
  expect(INGEST_ACCEPT).toBe(
34
- ".txt,.md,.html,.htm,.csv,.rtf,.docx,.doc,.pptx,.xlsx,.odt,.pdf,.png,.jpg,.jpeg,.webp,.gif,.heic,.svg,.drawio,.mp3,.m4a,.wav,.aac,.mp4,.m4v,.mov",
34
+ ".txt,.md,.html,.htm,.csv,.rtf,.docx,.doc,.pptx,.ppt,.xlsx,.xls,.odt,.ods,.odp,.epub,.pdf,.png,.jpg,.jpeg,.webp,.gif,.heic,.svg,.drawio,.mp3,.m4a,.wav,.aac,.mp4,.m4v,.mov",
35
35
  );
36
36
  });
37
37
 
38
38
  it("derives SUPPORTED_LABEL from the unique group labels", () => {
39
39
  expect(SUPPORTED_LABEL).toBe(`Supported: ${humanLabels().join(", ")}`);
40
40
  expect(SUPPORTED_LABEL).toBe(
41
- "Supported: Text, Markdown, HTML, CSV, RTF, Word, PowerPoint, Excel, OpenDocument, PDF, Image, Diagram, Audio, Video",
41
+ "Supported: Text, Markdown, HTML, CSV, RTF, Word, PowerPoint, Excel, OpenDocument, EPUB, PDF, Image, Diagram, Audio, Video",
42
42
  );
43
43
  });
44
44
  });
@@ -0,0 +1,65 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { INGESTION_FORMATS, byMime } from "../registry";
3
+ import {
4
+ INGESTION_SURFACES,
5
+ formatsFor,
6
+ surfaceAdmits,
7
+ acceptFor,
8
+ supportedLabelFor,
9
+ maxBytesFor,
10
+ } from "../surfaces";
11
+
12
+ describe("ingestion surfaces", () => {
13
+ it("every registry entry carries a family", () => {
14
+ for (const entry of INGESTION_FORMATS) {
15
+ expect(entry.family).toMatch(
16
+ /^(text|document|image|diagram|audio|video)$/,
17
+ );
18
+ }
19
+ });
20
+
21
+ it("every surface admits at least one format, in registry order", () => {
22
+ for (const key of Object.keys(INGESTION_SURFACES) as Array<
23
+ keyof typeof INGESTION_SURFACES
24
+ >) {
25
+ const formats = formatsFor(key);
26
+ expect(formats.length).toBeGreaterThan(0);
27
+ const order = formats.map((f) => INGESTION_FORMATS.indexOf(f));
28
+ expect(order).toEqual([...order].sort((a, b) => a - b));
29
+ }
30
+ });
31
+
32
+ it("company-md admits text and documents only", () => {
33
+ expect(surfaceAdmits("company-md", byMime("application/pdf")!)).toBe(true);
34
+ expect(surfaceAdmits("company-md", byMime("text/csv")!)).toBe(true);
35
+ expect(surfaceAdmits("company-md", byMime("image/png")!)).toBe(false);
36
+ expect(surfaceAdmits("company-md", byMime("audio/mpeg")!)).toBe(false);
37
+ expect(acceptFor("company-md")).toBe(
38
+ ".txt,.md,.html,.htm,.csv,.rtf,.docx,.doc,.pptx,.ppt,.xlsx,.xls,.odt,.ods,.odp,.epub,.pdf",
39
+ );
40
+ expect(supportedLabelFor("company-md")).toBe(
41
+ "Supported: Text, Markdown, HTML, CSV, RTF, Word, PowerPoint, Excel, OpenDocument, EPUB, PDF",
42
+ );
43
+ });
44
+
45
+ it("org-chart adds images and diagrams but never media", () => {
46
+ expect(surfaceAdmits("org-chart", byMime("image/heic")!)).toBe(true);
47
+ expect(surfaceAdmits("org-chart", byMime("image/svg+xml")!)).toBe(true);
48
+ expect(surfaceAdmits("org-chart", byMime("video/mp4")!)).toBe(false);
49
+ expect(acceptFor("org-chart")).toBe(
50
+ ".txt,.md,.html,.htm,.csv,.rtf,.docx,.doc,.pptx,.ppt,.xlsx,.xls,.odt,.ods,.odp,.epub,.pdf,.png,.jpg,.jpeg,.webp,.gif,.heic,.svg,.drawio",
51
+ );
52
+ });
53
+
54
+ it("derives the byte ceiling from the admitted formats and the surface cap", () => {
55
+ expect(maxBytesFor("company-md")).toBe(50_000_000);
56
+ expect(maxBytesFor("org-chart")).toBe(10 * 1024 * 1024);
57
+ for (const key of ["company-md", "org-chart"] as const) {
58
+ const ceiling = INGESTION_SURFACES[key].maxBytes ?? Infinity;
59
+ expect(maxBytesFor(key)).toBeLessThanOrEqual(ceiling);
60
+ expect(maxBytesFor(key)).toBeLessThanOrEqual(
61
+ Math.max(...formatsFor(key).map((f) => f.maxBytes)),
62
+ );
63
+ }
64
+ });
65
+ });
@@ -1,13 +1,19 @@
1
1
  /**
2
2
  * Ingestion Domain Barrel
3
3
  *
4
- * Re-exports the public document-ingestion format vocabulary.
4
+ * Re-exports the public document-ingestion format vocabulary and the
5
+ * per-surface views derived from it.
5
6
  * Import from '@company-semantics/contracts/ingestion'.
6
7
  *
7
- * @see ADR-CONT-075 for design rationale
8
+ * @see ADR-CONTRACTS-075 (registry), ADR-CONTRACTS-159 (surfaces)
8
9
  */
9
10
 
10
- export type { IngestionFormat } from "./types";
11
+ export type {
12
+ IngestionFamily,
13
+ IngestionFormat,
14
+ IngestionSurface,
15
+ IngestionSurfaceKey,
16
+ } from "./types";
11
17
 
12
18
  export {
13
19
  INGESTION_FORMATS,
@@ -17,3 +23,12 @@ export {
17
23
  INGEST_ACCEPT,
18
24
  SUPPORTED_LABEL,
19
25
  } from "./registry";
26
+
27
+ export {
28
+ INGESTION_SURFACES,
29
+ formatsFor,
30
+ surfaceAdmits,
31
+ acceptFor,
32
+ supportedLabelFor,
33
+ maxBytesFor,
34
+ } from "./surfaces";
@@ -21,144 +21,203 @@ import type { IngestionFormat } from "./types";
21
21
  export const INGESTION_FORMATS: readonly IngestionFormat[] = [
22
22
  {
23
23
  mime: "text/plain",
24
+ family: "text",
24
25
  extensions: [".txt"],
25
26
  label: "Text",
26
27
  maxBytes: 10_000_000,
27
28
  },
28
29
  {
29
30
  mime: "text/markdown",
31
+ family: "text",
30
32
  extensions: [".md"],
31
33
  label: "Markdown",
32
34
  maxBytes: 10_000_000,
33
35
  },
34
36
  {
35
37
  mime: "text/html",
38
+ family: "text",
36
39
  extensions: [".html", ".htm"],
37
40
  label: "HTML",
38
41
  maxBytes: 10_000_000,
39
42
  },
40
43
  {
41
44
  mime: "text/csv",
45
+ family: "text",
42
46
  extensions: [".csv"],
43
47
  label: "CSV",
44
48
  maxBytes: 25_000_000,
45
49
  },
46
50
  {
47
51
  mime: "application/rtf",
52
+ family: "document",
48
53
  extensions: [".rtf"],
49
54
  label: "RTF",
50
55
  maxBytes: 25_000_000,
51
56
  },
52
57
  {
53
58
  mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
59
+ family: "document",
54
60
  extensions: [".docx"],
55
61
  label: "Word",
56
62
  maxBytes: 25_000_000,
57
63
  },
58
64
  {
59
65
  mime: "application/msword",
66
+ family: "document",
60
67
  extensions: [".doc"],
61
68
  label: "Word",
62
69
  maxBytes: 25_000_000,
63
70
  },
64
71
  {
65
72
  mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
73
+ family: "document",
66
74
  extensions: [".pptx"],
67
75
  label: "PowerPoint",
68
76
  maxBytes: 50_000_000,
69
77
  },
78
+ {
79
+ mime: "application/vnd.ms-powerpoint",
80
+ family: "document",
81
+ extensions: [".ppt"],
82
+ label: "PowerPoint",
83
+ maxBytes: 50_000_000,
84
+ },
70
85
  {
71
86
  mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
87
+ family: "document",
72
88
  extensions: [".xlsx"],
73
89
  label: "Excel",
74
90
  maxBytes: 50_000_000,
75
91
  },
92
+ {
93
+ mime: "application/vnd.ms-excel",
94
+ family: "document",
95
+ extensions: [".xls"],
96
+ label: "Excel",
97
+ maxBytes: 50_000_000,
98
+ },
76
99
  {
77
100
  mime: "application/vnd.oasis.opendocument.text",
101
+ family: "document",
78
102
  extensions: [".odt"],
79
103
  label: "OpenDocument",
80
104
  maxBytes: 25_000_000,
81
105
  },
106
+ {
107
+ mime: "application/vnd.oasis.opendocument.spreadsheet",
108
+ family: "document",
109
+ extensions: [".ods"],
110
+ label: "OpenDocument",
111
+ maxBytes: 50_000_000,
112
+ },
113
+ {
114
+ mime: "application/vnd.oasis.opendocument.presentation",
115
+ family: "document",
116
+ extensions: [".odp"],
117
+ label: "OpenDocument",
118
+ maxBytes: 50_000_000,
119
+ },
120
+ {
121
+ mime: "application/epub+zip",
122
+ family: "document",
123
+ extensions: [".epub"],
124
+ label: "EPUB",
125
+ maxBytes: 25_000_000,
126
+ },
82
127
  {
83
128
  mime: "application/pdf",
129
+ family: "document",
84
130
  extensions: [".pdf"],
85
131
  label: "PDF",
86
132
  maxBytes: 50_000_000,
87
133
  },
88
134
  {
89
135
  mime: "image/png",
136
+ family: "image",
90
137
  extensions: [".png"],
91
138
  label: "Image",
92
139
  maxBytes: 25_000_000,
93
140
  },
94
141
  {
95
142
  mime: "image/jpeg",
143
+ family: "image",
96
144
  extensions: [".jpg", ".jpeg"],
97
145
  label: "Image",
98
146
  maxBytes: 25_000_000,
99
147
  },
100
148
  {
101
149
  mime: "image/webp",
150
+ family: "image",
102
151
  extensions: [".webp"],
103
152
  label: "Image",
104
153
  maxBytes: 25_000_000,
105
154
  },
106
155
  {
107
156
  mime: "image/gif",
157
+ family: "image",
108
158
  extensions: [".gif"],
109
159
  label: "Image",
110
160
  maxBytes: 25_000_000,
111
161
  },
112
162
  {
113
163
  mime: "image/heic",
164
+ family: "image",
114
165
  extensions: [".heic"],
115
166
  label: "Image",
116
167
  maxBytes: 25_000_000,
117
168
  },
118
169
  {
119
170
  mime: "image/svg+xml",
171
+ family: "diagram",
120
172
  extensions: [".svg"],
121
173
  label: "Diagram",
122
174
  maxBytes: 10_000_000,
123
175
  },
124
176
  {
125
177
  mime: "application/vnd.jgraph.mxfile",
178
+ family: "diagram",
126
179
  extensions: [".drawio"],
127
180
  label: "Diagram",
128
181
  maxBytes: 10_000_000,
129
182
  },
130
183
  {
131
184
  mime: "audio/mpeg",
185
+ family: "audio",
132
186
  extensions: [".mp3"],
133
187
  label: "Audio",
134
188
  maxBytes: 500_000_000,
135
189
  },
136
190
  {
137
191
  mime: "audio/x-m4a",
192
+ family: "audio",
138
193
  extensions: [".m4a"],
139
194
  label: "Audio",
140
195
  maxBytes: 500_000_000,
141
196
  },
142
197
  {
143
198
  mime: "audio/wav",
199
+ family: "audio",
144
200
  extensions: [".wav"],
145
201
  label: "Audio",
146
202
  maxBytes: 500_000_000,
147
203
  },
148
204
  {
149
205
  mime: "audio/aac",
206
+ family: "audio",
150
207
  extensions: [".aac"],
151
208
  label: "Audio",
152
209
  maxBytes: 500_000_000,
153
210
  },
154
211
  {
155
212
  mime: "video/mp4",
213
+ family: "video",
156
214
  extensions: [".mp4", ".m4v"],
157
215
  label: "Video",
158
216
  maxBytes: 500_000_000,
159
217
  },
160
218
  {
161
219
  mime: "video/quicktime",
220
+ family: "video",
162
221
  extensions: [".mov"],
163
222
  label: "Video",
164
223
  maxBytes: 500_000_000,
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Ingestion Surfaces
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
7
+ * `INGESTION_FORMATS` through that declaration. No surface re-spells a MIME.
8
+ *
9
+ * Invariants:
10
+ * - Every surface admits at least one registry format.
11
+ * - `maxBytesFor` never exceeds the registry cap of any admitted format, and
12
+ * never exceeds the surface ceiling when one is declared.
13
+ *
14
+ * @see ADR-CONTRACTS-159 for design rationale
15
+ */
16
+
17
+ import { INGESTION_FORMATS } from "./registry";
18
+ import type {
19
+ IngestionFormat,
20
+ IngestionSurface,
21
+ IngestionSurfaceKey,
22
+ } from "./types";
23
+
24
+ /** 10 MiB — the org-chart ceiling (see the surface JSDoc below). */
25
+ const ORG_CHART_MAX_BYTES = 10 * 1024 * 1024;
26
+
27
+ /**
28
+ * The canonical surface declarations.
29
+ *
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.
37
+ */
38
+ export const INGESTION_SURFACES: Readonly<
39
+ Record<IngestionSurfaceKey, IngestionSurface>
40
+ > = {
41
+ "company-md": {
42
+ key: "company-md",
43
+ families: ["text", "document"],
44
+ },
45
+ "org-chart": {
46
+ key: "org-chart",
47
+ families: ["text", "document", "image", "diagram"],
48
+ maxBytes: ORG_CHART_MAX_BYTES,
49
+ },
50
+ };
51
+
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));
56
+ }
57
+
58
+ /** True when the surface admits this registry format. */
59
+ export function surfaceAdmits(
60
+ surface: IngestionSurfaceKey,
61
+ format: IngestionFormat,
62
+ ): boolean {
63
+ return INGESTION_SURFACES[surface].families.includes(format.family);
64
+ }
65
+
66
+ /** Comma-joined extension string for the surface's file input `accept`. */
67
+ export function acceptFor(surface: IngestionSurfaceKey): string {
68
+ return formatsFor(surface)
69
+ .flatMap((entry) => [...entry.extensions])
70
+ .join(",");
71
+ }
72
+
73
+ /** "Supported: …" copy naming every label group the surface admits. */
74
+ export function supportedLabelFor(surface: IngestionSurfaceKey): string {
75
+ const labels = [...new Set(formatsFor(surface).map((entry) => entry.label))];
76
+ return `Supported: ${labels.join(", ")}`;
77
+ }
78
+
79
+ /**
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.
83
+ */
84
+ export function maxBytesFor(surface: IngestionSurfaceKey): number {
85
+ const registryMax = formatsFor(surface).reduce(
86
+ (max, entry) => Math.max(max, entry.maxBytes),
87
+ 0,
88
+ );
89
+ const ceiling = INGESTION_SURFACES[surface].maxBytes;
90
+ return ceiling === undefined ? registryMax : Math.min(registryMax, ceiling);
91
+ }
@@ -15,6 +15,18 @@
15
15
  * @see ADR-CONT-075 for design rationale
16
16
  */
17
17
 
18
+ /**
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
21
+ * byte ceiling for that surface derive from the registry through this field.
22
+ * Public and semantic — NOT the backend normalizer dispatch (`canonicalKind` /
23
+ * `normalizerKey` stay backend-local, ADR-CONTRACTS-075).
24
+ *
25
+ * @see ADR-CONTRACTS-159
26
+ */
27
+ export type IngestionFamily =
28
+ "text" | "document" | "image" | "diagram" | "audio" | "video";
29
+
18
30
  /**
19
31
  * One accepted upload format, keyed by its canonical MIME type.
20
32
  *
@@ -24,10 +36,13 @@
24
36
  * - `label` is the human display GROUP and is intentionally shared across
25
37
  * related formats (`.docx` + `.doc` are both `Word`; the rasters are all
26
38
  * `Image`; `.svg` + `.drawio` are `Diagram`).
39
+ * - `family` is the semantic family a surface selects on; every entry has one.
27
40
  */
28
41
  export interface IngestionFormat {
29
42
  /** Canonical MIME type (the registry key). */
30
43
  mime: string;
44
+ /** Semantic family the format belongs to (what a surface selects on). */
45
+ family: IngestionFamily;
31
46
  /** Lowercase, dot-prefixed filename extensions for this format. */
32
47
  extensions: readonly string[];
33
48
  /** Human display group label (e.g. `Word`, `Image`, `Audio`). */
@@ -35,3 +50,24 @@ export interface IngestionFormat {
35
50
  /** Per-format upload byte cap. */
36
51
  maxBytes: number;
37
52
  }
53
+
54
+ /** The upload surfaces that consume the registry. */
55
+ export type IngestionSurfaceKey = "company-md" | "org-chart";
56
+
57
+ /**
58
+ * What one upload surface consumes: the families it admits and, optionally, a
59
+ * byte ceiling tighter than the registry caps of those families.
60
+ *
61
+ * @see ADR-CONTRACTS-159
62
+ */
63
+ export interface IngestionSurface {
64
+ /** The surface this declaration describes. */
65
+ key: IngestionSurfaceKey;
66
+ /** Families the surface admits; a format outside them is refused (415). */
67
+ families: readonly IngestionFamily[];
68
+ /**
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.
71
+ */
72
+ maxBytes?: number;
73
+ }