@company-semantics/contracts 50.0.0 → 51.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/README.md CHANGED
@@ -11,7 +11,10 @@ until they are proven stable.
11
11
  ## Usage
12
12
 
13
13
  ```typescript
14
- import type { IntentCategory, InsightConfidence } from '@company-semantics/contracts'
14
+ import type {
15
+ IntentCategory,
16
+ InsightConfidence,
17
+ } from "@company-semantics/contracts";
15
18
  ```
16
19
 
17
20
  ## What belongs here
@@ -33,6 +36,7 @@ It does not emit `.d.ts` files. Consumers are expected to compile TS.
33
36
  ## Versioning
34
37
 
35
38
  Follows semver:
39
+
36
40
  - Additive vocabulary → minor bump
37
41
  - Renaming or removal → major bump
38
42
 
@@ -41,6 +45,7 @@ Follows semver:
41
45
  Releases are published automatically via GitHub Actions.
42
46
 
43
47
  Steps:
48
+
44
49
  1. `npm version patch`
45
50
  2. `git push origin main --tags`
46
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "50.0.0",
3
+ "version": "51.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -88,8 +88,8 @@
88
88
  "typecheck:ci": "NODE_OPTIONS='--max-old-space-size=4096' tsc -p scripts/ci/tsconfig.json",
89
89
  "lint": "eslint src/ scripts/ tests/ __tests__/ vitest.config.ts",
90
90
  "lint:md": "markdownlint-cli2 '**/*.md' '#node_modules' '#.ralph/worktrees' '#.claude/worktrees'",
91
- "format": "prettier --write src/",
92
- "format:check": "prettier --check src/",
91
+ "format": "prettier --write .",
92
+ "format:check": "prettier --check .",
93
93
  "lint:json": "node -e \"JSON.parse(require('fs').readFileSync('package.json'))\"",
94
94
  "prepare": "husky",
95
95
  "guard:export": "npx tsx scripts/ci/export-guard.ts",
@@ -101,9 +101,9 @@
101
101
  "guard:version-tag:json": "npx tsx scripts/ci/version-tag-guard.ts --json",
102
102
  "guard:decisions-deprecation": "npx tsx scripts/ci/decisions-deprecation-guard.ts",
103
103
  "guard:decisions-deprecation:json": "npx tsx scripts/ci/decisions-deprecation-guard.ts --json",
104
- "guard:quick": "GUARD_DEPENDENCY_ROOT=../ tsx ../company-semantics-ci/run-guards.ts --config scripts/ci/guard-entries.ts --quick",
105
- "ci:local": "pnpm typecheck && pnpm lint:md && pnpm guard:test && GUARD_DEPENDENCY_ROOT=../ pnpm guard",
106
- "guard": "GUARD_DEPENDENCY_ROOT=../ tsx ../company-semantics-ci/run-guards.ts --config scripts/ci/guard-entries.ts",
104
+ "guard:quick": "sh scripts/ci/run-guard.sh --quick",
105
+ "ci:local": "pnpm typecheck && pnpm lint:md && pnpm guard:test && pnpm guard",
106
+ "guard": "sh scripts/ci/run-guard.sh",
107
107
  "guard:test": "NODE_OPTIONS='--max-old-space-size=4096' vitest run scripts/ci/__tests__",
108
108
  "release": "npx tsx scripts/release.ts",
109
109
  "prepublishOnly": "echo 'ERROR: Publishing is CI-only via tag push. Use pnpm release instead.' && exit 1",
@@ -120,13 +120,13 @@
120
120
  "generate:openapi-routes": "tsx scripts/generate-openapi-routes.ts",
121
121
  "generate:openapi-routes:check": "tsx scripts/generate-openapi-routes.ts --check",
122
122
  "generate:api-types:check": "openapi-typescript openapi/backend.yaml -o /tmp/cs-api-types-check.ts && diff -q src/api/generated.ts /tmp/cs-api-types-check.ts",
123
- "generate:current": "tsx ../company-semantics-ci/scripts/generate-current.ts",
124
- "adr:new": "tsx ../company-semantics-control/scripts/adr/adr-new-cli.ts",
125
- "repo-map": "tsx ../company-semantics-ci/scripts/generate-repo-map.ts --roots src --name company-semantics-contracts --write",
126
- "sync:workflow-pins": "tsx ../company-semantics-ci/scripts/sync-workflow-pins.ts --write",
127
- "repo-map:check": "tsx ../company-semantics-ci/scripts/generate-repo-map.ts --roots src --name company-semantics-contracts --check",
128
- "readme-api": "tsx ../company-semantics-ci/scripts/generate-readme-api.ts --roots src --write --jsdoc",
129
- "readme-api:check": "tsx ../company-semantics-ci/scripts/generate-readme-api.ts --roots src --check --jsdoc",
123
+ "generate:current": "tsx \"$(git rev-parse --path-format=absolute --git-common-dir)/../../company-semantics-ci/scripts/generate-current.ts\"",
124
+ "adr:new": "tsx \"$(git rev-parse --path-format=absolute --git-common-dir)/../../company-semantics-control/scripts/adr/adr-new-cli.ts\"",
125
+ "repo-map": "tsx \"$(git rev-parse --path-format=absolute --git-common-dir)/../../company-semantics-ci/scripts/generate-repo-map.ts\" --roots src --name company-semantics-contracts --write",
126
+ "sync:workflow-pins": "tsx \"$(git rev-parse --path-format=absolute --git-common-dir)/../../company-semantics-ci/scripts/sync-workflow-pins.ts\" --write",
127
+ "repo-map:check": "tsx \"$(git rev-parse --path-format=absolute --git-common-dir)/../../company-semantics-ci/scripts/generate-repo-map.ts\" --roots src --name company-semantics-contracts --check",
128
+ "readme-api": "tsx \"$(git rev-parse --path-format=absolute --git-common-dir)/../../company-semantics-ci/scripts/generate-readme-api.ts\" --roots src --write --jsdoc",
129
+ "readme-api:check": "tsx \"$(git rev-parse --path-format=absolute --git-common-dir)/../../company-semantics-ci/scripts/generate-readme-api.ts\" --roots src --check --jsdoc",
130
130
  "validate:amp": "tsx scripts/validate-amp.ts"
131
131
  },
132
132
  "packageManager": "pnpm@10.25.0",
@@ -159,7 +159,7 @@
159
159
  "overrides": {
160
160
  "brace-expansion@<5.0.9": ">=5.0.9",
161
161
  "minimatch@<10.2.3": ">=10.2.3",
162
- "nanoid@<3.3.17": ">=3.3.17",
162
+ "nanoid@<3.3.18": ">=3.3.18",
163
163
  "js-yaml@>=4.0.0 <4.3.1": ">=4.3.1 <5.0.0",
164
164
  "js-yaml@>=5.0.0 <5.2.2": ">=5.2.2 <6.0.0",
165
165
  "ajv@<6.14.0": ">=6.14.0",
@@ -176,5 +176,5 @@
176
176
  "*.md": "markdownlint-cli2",
177
177
  "package.json": "node -e \"JSON.parse(require('fs').readFileSync('package.json'))\""
178
178
  },
179
- "securityRequirementsVersion": "dfe68368cb5b7fdc67290fba5cd5a920c9a2ca9c20a9e297232a467b2022e5b5"
179
+ "securityRequirementsVersion": "2dcea336317c1136c8d94798b1cadc9c115febf574d041a173974a5f7959de0b"
180
180
  }
@@ -234,16 +234,16 @@ describe("resource-keys: companyMdAccessRequests (per-doc identity)", () => {
234
234
  });
235
235
  });
236
236
 
237
- describe("resource-keys: companyMdDocVersions (per-doc version list)", () => {
237
+ describe("resource-keys: companyMdDocHistory (per-doc history list)", () => {
238
238
  const DOC_ID = "44444444-4444-4444-8444-444444444444";
239
239
  const key: ResourceKey = {
240
- type: "companyMdDocVersions",
240
+ type: "companyMdDocHistory",
241
241
  orgId: ORG_ID,
242
242
  docId: DOC_ID,
243
243
  };
244
244
 
245
245
  it("serialises to [type, orgId, docId]", () => {
246
- expect(toQueryKey(key)).toEqual(["companyMdDocVersions", ORG_ID, DOC_ID]);
246
+ expect(toQueryKey(key)).toEqual(["companyMdDocHistory", ORG_ID, DOC_ID]);
247
247
  });
248
248
 
249
249
  it("round-trips through fromQueryKey", () => {
@@ -261,7 +261,7 @@ describe("resource-keys: companyMdDocVersions (per-doc version list)", () => {
261
261
 
262
262
  it("does NOT collide with companyMdAccessRequests, which shares its shape", () => {
263
263
  // Same segments, same field name, same document — only the type tag tells
264
- // the two apart. An owner's access-request inbox and a version list must
264
+ // the two apart. An owner's access-request inbox and a history list must
265
265
  // never invalidate each other.
266
266
  const requestsKey: ResourceKey = {
267
267
  type: "companyMdAccessRequests",
@@ -277,7 +277,7 @@ describe("resource-keys: companyMdDocVersion (composite version identity)", () =
277
277
  // The second composite member. Its identity is (docId, versionId), so it
278
278
  // exercises the same four-segment path `commentThreads` opened — and, unlike
279
279
  // that key, it has a SIBLING of the same doc scope one segment shorter
280
- // (companyMdDocVersions), which is the collision the split exists to avoid.
280
+ // (companyMdDocHistory), which is the collision the split exists to avoid.
281
281
  const DOC_ID = "44444444-4444-4444-8444-444444444444";
282
282
  const VERSION_ID = "66666666-6666-4666-8666-666666666666";
283
283
  const key: ResourceKey = {
@@ -319,12 +319,12 @@ describe("resource-keys: companyMdDocVersion (composite version identity)", () =
319
319
  expect(matchesResourceKey(toQueryKey(key), other)).toBe(false);
320
320
  });
321
321
 
322
- it("is NOT invalidated by a sweep of the document's version LIST", () => {
322
+ it("is NOT invalidated by a sweep of the document's history LIST", () => {
323
323
  // The whole reason the two members are distinct. A restore appends to the
324
- // history and so invalidates `companyMdDocVersions`; the sealed bodies
324
+ // history and so invalidates `companyMdDocHistory`; the sealed bodies
325
325
  // already fetched are immutable and must survive it.
326
326
  const listKey: ResourceKey = {
327
- type: "companyMdDocVersions",
327
+ type: "companyMdDocHistory",
328
328
  orgId: ORG_ID,
329
329
  docId: DOC_ID,
330
330
  };
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = '7ed2b7218b4b' as const;
3
- export const SPEC_HASH_FULL = '7ed2b7218b4b4a160ba6e9d8854f2e9277d540c33658448d9a0452e96f1cd581' as const;
2
+ export const SPEC_HASH = '6dcd82441a54' as const;
3
+ export const SPEC_HASH_FULL = '6dcd82441a54409faaa94d94f36a43cd72894c7e655fb49f5eb758a0963c9a43' as const;
@@ -1548,15 +1548,15 @@ export interface paths {
1548
1548
  patch?: never;
1549
1549
  trace?: never;
1550
1550
  };
1551
- "/api/company-md/docs/{id}/versions": {
1551
+ "/api/company-md/docs/{id}/history": {
1552
1552
  parameters: {
1553
1553
  query?: never;
1554
1554
  header?: never;
1555
1555
  path?: never;
1556
1556
  cookie?: never;
1557
1557
  };
1558
- /** List a company.md document’s version history (metadata only) */
1559
- get: operations["listCompanyMdDocVersions"];
1558
+ /** List a company.md document’s merged history (versions and lifecycle events) */
1559
+ get: operations["listCompanyMdDocHistory"];
1560
1560
  put?: never;
1561
1561
  post?: never;
1562
1562
  delete?: never;
@@ -5034,10 +5034,11 @@ export interface components {
5034
5034
  CompanyMdMentionableResponse: {
5035
5035
  items: components["schemas"]["CompanyMdMentionableCandidate"][];
5036
5036
  };
5037
- /** @description A page of a company.md document’s version history, plus the opaque token a restore is gated on. */
5038
- CompanyMdDocVersionsResponse: {
5037
+ /** @description A page of a company.md document’s merged History — versions and lifecycle events under one cursor — plus the opaque token a restore is gated on. */
5038
+ CompanyMdDocHistoryResponse: {
5039
5039
  currentRevision: string;
5040
- entries: components["schemas"]["CompanyMdDocVersionEntry"][];
5040
+ items: components["schemas"]["CompanyMdDocHistoryItem"][];
5041
+ withheldCategories: ("version" | "source" | "lifecycle" | "access" | "sync")[];
5041
5042
  nextCursor: string | null;
5042
5043
  };
5043
5044
  /** @description One sealed company.md version — its metadata and the body it sealed. */
@@ -6532,12 +6533,55 @@ export interface components {
6532
6533
  displayName: string;
6533
6534
  avatarUrl: string | null;
6534
6535
  };
6536
+ /** @description One item in a document’s merged History: a sealed version (or the in-progress session) nested as `version`, or a lifecycle event. */
6537
+ CompanyMdDocHistoryItem: {
6538
+ /** @constant */
6539
+ kind: "version";
6540
+ mergeId: string | null;
6541
+ occurredAt: string;
6542
+ /** @constant */
6543
+ category: "version";
6544
+ version: components["schemas"]["CompanyMdDocVersionEntry"];
6545
+ } | {
6546
+ /** @constant */
6547
+ kind: "event";
6548
+ mergeId: string;
6549
+ occurredAt: string;
6550
+ /** @enum {string} */
6551
+ category: "version" | "source" | "lifecycle" | "access" | "sync";
6552
+ /** @enum {string} */
6553
+ eventType: "doc_created" | "source_added" | "source_removed" | "content_generated" | "external_sync_completed" | "access_granted" | "access_revoked" | "access_updated" | "sharing_policy_changed" | "visibility_changed" | "owner_transferred" | "discoverability_changed";
6554
+ actor: components["schemas"]["CompanyMdDocVersionActor"] | null;
6555
+ subject: string | null;
6556
+ target: components["schemas"]["CompanyMdDocHistoryTarget"] | null;
6557
+ previousValue: string | null;
6558
+ newValue: string | null;
6559
+ };
6535
6560
  /** @description The singular actor a version is attributed to, in product vocabulary. */
6536
6561
  CompanyMdDocVersionActor: {
6537
6562
  /** @enum {string} */
6538
6563
  type: "member" | "agent" | "system";
6539
6564
  userId: string;
6540
6565
  };
6566
+ /** @description What a history event acted on: an ACL principal (member, unit, or the whole org) or the extracted source artifact. */
6567
+ CompanyMdDocHistoryTarget: {
6568
+ /** @constant */
6569
+ type: "member";
6570
+ userId: string;
6571
+ } | {
6572
+ /** @constant */
6573
+ type: "unit";
6574
+ unitId: string;
6575
+ } | {
6576
+ /** @constant */
6577
+ type: "org";
6578
+ } | {
6579
+ /** @constant */
6580
+ type: "source";
6581
+ filename: string | null;
6582
+ mime: string;
6583
+ byteSize: number;
6584
+ };
6541
6585
  /** @description An ACL-admitted context-doc discovery hit. */
6542
6586
  ContextDocDiscoveryHit: {
6543
6587
  id: string;
@@ -9079,7 +9123,7 @@ export interface operations {
9079
9123
  };
9080
9124
  };
9081
9125
  };
9082
- listCompanyMdDocVersions: {
9126
+ listCompanyMdDocHistory: {
9083
9127
  parameters: {
9084
9128
  query?: {
9085
9129
  cursor?: string;
@@ -9093,13 +9137,13 @@ export interface operations {
9093
9137
  };
9094
9138
  requestBody?: never;
9095
9139
  responses: {
9096
- /** @description A page of version metadata, plus the document’s live revision token */
9140
+ /** @description A page of merged history items, plus the document’s live revision token and the categories withheld for this reader */
9097
9141
  200: {
9098
9142
  headers: {
9099
9143
  [name: string]: unknown;
9100
9144
  };
9101
9145
  content: {
9102
- "application/json": components["schemas"]["CompanyMdDocVersionsResponse"];
9146
+ "application/json": components["schemas"]["CompanyMdDocHistoryResponse"];
9103
9147
  };
9104
9148
  };
9105
9149
  /** @description Invalid cursor or limit */
@@ -50,6 +50,7 @@ export const openApiRoutes = {
50
50
  '/api/company-md/docs/{id}/context-bank/upload': ['POST'],
51
51
  '/api/company-md/docs/{id}/context-bank/{contextDocId}': ['DELETE'],
52
52
  '/api/company-md/docs/{id}/context-bank/{contextDocId}/order': ['PATCH'],
53
+ '/api/company-md/docs/{id}/history': ['GET'],
53
54
  '/api/company-md/docs/{id}/mentionable': ['GET'],
54
55
  '/api/company-md/docs/{id}/sharing': ['GET'],
55
56
  '/api/company-md/docs/{id}/sharing/acl': ['POST'],
@@ -57,7 +58,6 @@ export const openApiRoutes = {
57
58
  '/api/company-md/docs/{id}/sharing/policy': ['PUT'],
58
59
  '/api/company-md/docs/{id}/title': ['PUT'],
59
60
  '/api/company-md/docs/{id}/transfer-owner': ['POST'],
60
- '/api/company-md/docs/{id}/versions': ['GET'],
61
61
  '/api/company-md/docs/{id}/versions/{versionId}': ['GET'],
62
62
  '/api/company-md/docs/{id}/versions/{versionId}/restore': ['POST'],
63
63
  '/api/company-md/extract': ['POST'],
@@ -38,16 +38,19 @@ export type ResourceKey =
38
38
  // a bare slug does not identify a document. That field name is a wart to be
39
39
  // contained, not propagated — a new key gets the honest name.
40
40
  | { type: "companyMdAccessRequests"; orgId: string; docId: string }
41
- // The sealed version history of ONE document (PRD-00927) — the metadata-only
42
- // list the history panel pages through. Keyed by `docId` for the same reason
43
- // `companyMdAccessRequests` is: the honest name for the stable document id,
44
- // rather than propagating `companyMdDoc`'s legacy `slug` field name.
45
- | { type: "companyMdDocVersions"; orgId: string; docId: string }
41
+ // The merged history feed of ONE document (PRD-00938) — sealed versions plus
42
+ // lifecycle events, the metadata-only list the history panel pages through.
43
+ // Named `companyMdDocHistory`, not `companyMdDocVersions`: the history is the
44
+ // list, a version is a thing in it (see `companyMdDocVersion` below). Keyed by
45
+ // `docId` for the same reason `companyMdAccessRequests` is: the honest name
46
+ // for the stable document id, rather than propagating `companyMdDoc`'s legacy
47
+ // `slug` field name.
48
+ | { type: "companyMdDocHistory"; orgId: string; docId: string }
46
49
  // ONE sealed version, body included.
47
50
  //
48
51
  // A member of its own rather than a `versionId` segment folded into the list
49
52
  // key above, and the split is load-bearing. A restore APPENDS to the history,
50
- // so it must invalidate `companyMdDocVersions` — and because a sealed version
53
+ // so it must invalidate `companyMdDocHistory` — and because a sealed version
51
54
  // is immutable, every already-fetched body must SURVIVE that invalidation.
52
55
  // Fold the two together and each restore evicts the whole set of historical
53
56
  // bodies the reader just paged through, to re-fetch content that cannot have
@@ -96,7 +96,7 @@ const IDENTITY_FIELDS = {
96
96
  companyMdDoc: "slug",
97
97
  companyMdContextBank: "slug",
98
98
  companyMdAccessRequests: "docId",
99
- companyMdDocVersions: "docId",
99
+ companyMdDocHistory: "docId",
100
100
  orgUnit: "unitId",
101
101
  orgUnitChildren: "unitId",
102
102
  orgUnitAncestors: "unitId",
@@ -226,7 +226,7 @@ export function toQueryKey(key: ResourceKey): readonly string[] {
226
226
  case "companyMdContextBank":
227
227
  return [key.type, key.orgId, key.slug] as const;
228
228
  case "companyMdAccessRequests":
229
- case "companyMdDocVersions":
229
+ case "companyMdDocHistory":
230
230
  return [key.type, key.orgId, key.docId] as const;
231
231
 
232
232
  // Composite identity — subject CLASS then subject id, in that order, so the
@@ -60,7 +60,7 @@ export type ResourceEntryInput = z.input<typeof ResourceEntrySchema>;
60
60
  *
61
61
  * TWO VOCABULARY CAVEATS, both deliberate and both for ADR-CONTRACTS-120:
62
62
  *
63
- * 1. `resource` here is the {@link ResourceKey} type TAG (`companyMdDocVersions`).
63
+ * 1. `resource` here is the {@link ResourceKey} type TAG (`companyMdDocHistory`).
64
64
  * The registry that currently holds live rows is the app's copy, and its
65
65
  * `resource` values are PATH-shaped (`company-md/docs`, `orgs/:orgId/billing`).
66
66
  * The two do not line up, and reconciling them is not this PRD's work —
@@ -73,8 +73,9 @@ export const COMPANY_MD_VERSION_RESOURCE_ENTRIES = [
73
73
  {
74
74
  // The history panel is opened, never hydrated with the page — hence
75
75
  // background/P3. A restore appends to this list, so it is the row that
76
- // takes the invalidation.
77
- resource: "companyMdDocVersions",
76
+ // takes the invalidation. The history is the list, a version is a thing
77
+ // in it — hence `companyMdDocHistory` here and `companyMdDocVersion` below.
78
+ resource: "companyMdDocHistory",
78
79
  priority: "P3",
79
80
  hydrationPhase: "background",
80
81
  hydrationDepends: [],
@@ -96,7 +97,7 @@ export const COMPANY_MD_VERSION_RESOURCE_ENTRIES = [
96
97
  resource: "companyMdDocVersion",
97
98
  priority: "P3",
98
99
  hydrationPhase: "background",
99
- hydrationDepends: ["companyMdDocVersions"],
100
+ hydrationDepends: ["companyMdDocHistory"],
100
101
  mutationBehavior: "serial",
101
102
  staleTimeMs: 300_000,
102
103
  degradationSection: "core",