@company-semantics/contracts 44.4.0 → 45.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": "44.4.0",
3
+ "version": "45.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -130,7 +130,7 @@
130
130
  "zod": "^4.4.3"
131
131
  },
132
132
  "devDependencies": {
133
- "@types/node": "^26.1.2",
133
+ "@types/node": "^22.20.1",
134
134
  "amphtml-validator": "^1.0.38",
135
135
  "culori": "^4.0.2",
136
136
  "husky": "^9.1.7",
@@ -150,7 +150,6 @@
150
150
  "minimatch@<10.2.3": ">=10.2.3",
151
151
  "js-yaml@>=4.0.0 <4.3.0": ">=4.3.0 <5.0.0",
152
152
  "js-yaml@>=5.0.0 <5.2.2": ">=5.2.2 <6.0.0",
153
- "rollup@<4.59.0": ">=4.59.0",
154
153
  "picomatch@<4.0.4": ">=4.0.4",
155
154
  "esbuild@<0.28.1": ">=0.28.1",
156
155
  "linkify-it@<5.0.2": ">=5.0.2",
@@ -98,6 +98,37 @@ describe("resource-keys: system-scoped internalAdmin* types", () => {
98
98
  });
99
99
  });
100
100
 
101
+ describe("resource-keys: userMd (user-scoped singleton)", () => {
102
+ const USER_ID = "55555555-5555-4555-8555-555555555555";
103
+ const key: ResourceKey = { type: "userMd", userId: USER_ID };
104
+
105
+ it("toQueryKey produces [type, userId] with no doc identity", () => {
106
+ const qk = toQueryKey(key);
107
+ expect(qk).toEqual(["userMd", USER_ID]);
108
+ // A per-user singleton: the user IS the identity, so there is no third
109
+ // segment even though the payload is a work item (ADR-BE-487).
110
+ expect(qk).toHaveLength(2);
111
+ });
112
+
113
+ it("roundtrips through fromQueryKey", () => {
114
+ const parsed = fromQueryKey(toQueryKey(key));
115
+ expect(parsed).toEqual(key);
116
+ expect("orgId" in parsed).toBe(false);
117
+ });
118
+
119
+ it("matchesResourceKey is user-scoped: rejects another user's doc", () => {
120
+ const otherUser = "66666666-6666-4666-8666-666666666666";
121
+ expect(matchesResourceKey(toQueryKey(key), key)).toBe(true);
122
+ expect(matchesResourceKey(["userMd", otherUser], key)).toBe(false);
123
+ });
124
+
125
+ it("does not collide with the other user-scoped keys", () => {
126
+ const viewerKey: ResourceKey = { type: "viewer", userId: USER_ID };
127
+ expect(matchesResourceKey(toQueryKey(viewerKey), key)).toBe(false);
128
+ expect(matchesResourceKey(toQueryKey(key), viewerKey)).toBe(false);
129
+ });
130
+ });
131
+
101
132
  describe("resource-keys: companyMdAccessRequests (per-doc identity)", () => {
102
133
  const DOC_ID = "22222222-2222-4222-8222-222222222222";
103
134
  const key: ResourceKey = {
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = '9bcb0e0bc2cc' as const;
3
- export const SPEC_HASH_FULL = '9bcb0e0bc2cce27742a97dd5f7ef4cadcff268e8700f40554258301cee4e183e' as const;
2
+ export const SPEC_HASH = 'b10dd357bc4f' as const;
3
+ export const SPEC_HASH_FULL = 'b10dd357bc4fd990f9d6646954b3b1aa38f9dce4dd64ecfe8f5f83eb8f51a568' as const;
@@ -4736,8 +4736,8 @@ export interface components {
4736
4736
  extractionStatus: "pending" | "extracting" | "complete" | "failed";
4737
4737
  errorClass?: string | null;
4738
4738
  errorReason?: string | null;
4739
- createdAt: string;
4740
- updatedAt: string;
4739
+ createdAt?: string;
4740
+ updatedAt?: string;
4741
4741
  };
4742
4742
  UpdateCompanyMdTitleRequest: {
4743
4743
  title: string;
@@ -199,8 +199,16 @@ export const CompanyMdDocResponseSchema = z.object({
199
199
  */
200
200
  errorClass: z.string().nullable().optional(),
201
201
  errorReason: z.string().nullable().optional(),
202
- createdAt: z.string(),
203
- updatedAt: z.string(),
202
+ /**
203
+ * Withheld from non-readers — see `CompanyMdDocRelations` for why, and keep the
204
+ * two in lockstep. Present on every reader path; absent on the
205
+ * `locked_requestable` / `redacted_admin` stubs, where a moving timestamp is
206
+ * the last remaining side channel on a doc whose body, collaborators and
207
+ * structure are already withheld. Absence means "you were not told", not
208
+ * "never modified".
209
+ */
210
+ createdAt: z.string().optional(),
211
+ updatedAt: z.string().optional(),
204
212
  });
205
213
 
206
214
  // ---------------------------------------------------------------------------
@@ -275,8 +275,25 @@ export interface CompanyMdDocRelations {
275
275
  readonly inheritsFrom: string | null;
276
276
  readonly sources: readonly CompanyMdSource[];
277
277
  readonly dependencies: readonly CompanyMdDependency[];
278
- readonly createdAt: string;
279
- readonly updatedAt: string;
278
+ /**
279
+ * WITHHELD FROM NON-READERS, and optional for exactly that reason.
280
+ *
281
+ * A non-reader's doc read is a stub: `content` is `''`, and `sources` /
282
+ * `dependencies` / `members` are empty. These two timestamps were the one
283
+ * thing left on it that still reported activity INSIDE the document — that
284
+ * someone wrote to it, and roughly when. On a doc whose body, collaborators
285
+ * and structure are all withheld, a moving `updatedAt` is a side channel: poll
286
+ * it and you learn how busy a document you cannot open is.
287
+ *
288
+ * So they are absent on the `locked_requestable` / `redacted_admin` branches
289
+ * and present on every reader path. Optional rather than nullable because
290
+ * "withheld" is the absence of an answer, not an answer of `null` — and
291
+ * because an older consumer reading the new API sees a missing field, which is
292
+ * the shape it already tolerates for every other stub-withheld value here.
293
+ * Treat absence as "you were not told", never as "never modified".
294
+ */
295
+ readonly createdAt?: string;
296
+ readonly updatedAt?: string;
280
297
  }
281
298
 
282
299
  export type CompanyMdDoc = CompanyMdDocCore &
@@ -84,6 +84,12 @@ export type ResourceKey =
84
84
  | { type: "dismissedBanners"; userId: string }
85
85
  | { type: "userOrgs"; userId: string }
86
86
  | { type: "sessions"; userId: string }
87
+ // The viewer's personal doc (`/api/me/md`) — a per-user singleton, so it is
88
+ // keyed by userId alone with no doc identity segment. Its PAYLOAD is a work
89
+ // item, lazily materialized on first read (ADR-BE-487), but the resource is
90
+ // "this user's personal doc", not "work item <id>": a future generic
91
+ // `workItem` key would be a different resource with a different identity.
92
+ | { type: "userMd"; userId: string }
87
93
  | { type: "viewer"; userId: string };
88
94
 
89
95
  /**
@@ -131,6 +137,7 @@ const USER_SCOPED_TYPES = [
131
137
  "dismissedBanners",
132
138
  "userOrgs",
133
139
  "sessions",
140
+ "userMd",
134
141
  "viewer",
135
142
  ] as const;
136
143
 
@@ -186,6 +193,7 @@ export function toQueryKey(key: ResourceKey): readonly string[] {
186
193
  case "dismissedBanners":
187
194
  case "userOrgs":
188
195
  case "sessions":
196
+ case "userMd":
189
197
  case "viewer":
190
198
  return [key.type, key.userId] as const;
191
199