@company-semantics/contracts 44.3.0 → 44.5.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 +6 -6
- package/src/__tests__/resource-keys.test.ts +31 -0
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +38 -1
- package/src/content/schemas.ts +3 -1
- package/src/generated/openapi-routes.ts +1 -0
- package/src/permissions/share-api.ts +6 -3
- package/src/resource-keys.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@company-semantics/contracts",
|
|
3
|
-
"version": "44.
|
|
3
|
+
"version": "44.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
},
|
|
81
81
|
"scripts": {
|
|
82
82
|
"build": "tsc -b --noEmit",
|
|
83
|
-
"typecheck": "tsc -b --noEmit",
|
|
84
|
-
"typecheck:ci": "tsc -p scripts/ci/tsconfig.json",
|
|
83
|
+
"typecheck": "NODE_OPTIONS='--max-old-space-size=4096' tsc -b --noEmit",
|
|
84
|
+
"typecheck:ci": "NODE_OPTIONS='--max-old-space-size=4096' tsc -p scripts/ci/tsconfig.json",
|
|
85
85
|
"lint:md": "markdownlint-cli2 '**/*.md' '#node_modules' '#.ralph/worktrees' '#.claude/worktrees'",
|
|
86
86
|
"format": "prettier --write src/",
|
|
87
87
|
"format:check": "prettier --check src/",
|
|
@@ -99,11 +99,11 @@
|
|
|
99
99
|
"guard:quick": "GUARD_DEPENDENCY_ROOT=../ tsx ../company-semantics-ci/run-guards.ts --config scripts/ci/guard-entries.ts --quick",
|
|
100
100
|
"ci:local": "pnpm typecheck && pnpm lint:md && pnpm guard:test && GUARD_DEPENDENCY_ROOT=../ pnpm guard",
|
|
101
101
|
"guard": "GUARD_DEPENDENCY_ROOT=../ tsx ../company-semantics-ci/run-guards.ts --config scripts/ci/guard-entries.ts",
|
|
102
|
-
"guard:test": "vitest run scripts/ci/__tests__",
|
|
102
|
+
"guard:test": "NODE_OPTIONS='--max-old-space-size=4096' vitest run scripts/ci/__tests__",
|
|
103
103
|
"release": "npx tsx scripts/release.ts",
|
|
104
104
|
"prepublishOnly": "echo 'ERROR: Publishing is CI-only via tag push. Use pnpm release instead.' && exit 1",
|
|
105
|
-
"test": "bash scripts/vitest-run.sh run",
|
|
106
|
-
"test:run": "bash scripts/vitest-run.sh run",
|
|
105
|
+
"test": "NODE_OPTIONS='--max-old-space-size=4096' bash scripts/vitest-run.sh run",
|
|
106
|
+
"test:run": "NODE_OPTIONS='--max-old-space-size=4096' bash scripts/vitest-run.sh run",
|
|
107
107
|
"color": "tsx scripts/color.ts",
|
|
108
108
|
"generate:spec-hash": "tsx scripts/generate-spec-hash.ts",
|
|
109
109
|
"generate:spec-hash:check": "tsx scripts/generate-spec-hash.ts --check",
|
|
@@ -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 = '
|
|
3
|
-
export const SPEC_HASH_FULL = '
|
|
2
|
+
export const SPEC_HASH = '9bcb0e0bc2cc' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = '9bcb0e0bc2cce27742a97dd5f7ef4cadcff268e8700f40554258301cee4e183e' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -2879,6 +2879,23 @@ export interface paths {
|
|
|
2879
2879
|
patch?: never;
|
|
2880
2880
|
trace?: never;
|
|
2881
2881
|
};
|
|
2882
|
+
"/api/me/md": {
|
|
2883
|
+
parameters: {
|
|
2884
|
+
query?: never;
|
|
2885
|
+
header?: never;
|
|
2886
|
+
path?: never;
|
|
2887
|
+
cookie?: never;
|
|
2888
|
+
};
|
|
2889
|
+
/** Get (and lazily materialize) my personal doc */
|
|
2890
|
+
get: operations["getMyMd"];
|
|
2891
|
+
put?: never;
|
|
2892
|
+
post?: never;
|
|
2893
|
+
delete?: never;
|
|
2894
|
+
options?: never;
|
|
2895
|
+
head?: never;
|
|
2896
|
+
patch?: never;
|
|
2897
|
+
trace?: never;
|
|
2898
|
+
};
|
|
2882
2899
|
"/api/me/action-items": {
|
|
2883
2900
|
parameters: {
|
|
2884
2901
|
query?: never;
|
|
@@ -5550,7 +5567,7 @@ export interface components {
|
|
|
5550
5567
|
WorkItemDetailResponse: {
|
|
5551
5568
|
id: string;
|
|
5552
5569
|
/** @enum {string} */
|
|
5553
|
-
kind: "meeting" | "comm" | "doc";
|
|
5570
|
+
kind: "meeting" | "comm" | "doc" | "personal";
|
|
5554
5571
|
title: string;
|
|
5555
5572
|
content: string;
|
|
5556
5573
|
contentRedacted?: boolean;
|
|
@@ -10911,6 +10928,26 @@ export interface operations {
|
|
|
10911
10928
|
};
|
|
10912
10929
|
};
|
|
10913
10930
|
};
|
|
10931
|
+
getMyMd: {
|
|
10932
|
+
parameters: {
|
|
10933
|
+
query?: never;
|
|
10934
|
+
header?: never;
|
|
10935
|
+
path?: never;
|
|
10936
|
+
cookie?: never;
|
|
10937
|
+
};
|
|
10938
|
+
requestBody?: never;
|
|
10939
|
+
responses: {
|
|
10940
|
+
/** @description The caller-owned personal doc (kind personal), created on first access with an empty body */
|
|
10941
|
+
200: {
|
|
10942
|
+
headers: {
|
|
10943
|
+
[name: string]: unknown;
|
|
10944
|
+
};
|
|
10945
|
+
content: {
|
|
10946
|
+
"application/json": components["schemas"]["WorkItemDetailResponse"];
|
|
10947
|
+
};
|
|
10948
|
+
};
|
|
10949
|
+
};
|
|
10950
|
+
};
|
|
10914
10951
|
listMyActionItems: {
|
|
10915
10952
|
parameters: {
|
|
10916
10953
|
query?: never;
|
package/src/content/schemas.ts
CHANGED
|
@@ -217,7 +217,9 @@ export const CompanyMdDocResponseSchema = z.object({
|
|
|
217
217
|
*/
|
|
218
218
|
export const WorkItemDetailResponseSchema = z.object({
|
|
219
219
|
id: z.string(),
|
|
220
|
-
|
|
220
|
+
// `personal` is the /me/md page (ADR-BE-487): a per-(org, user) singleton
|
|
221
|
+
// riding the work_items substrate, served by getMyMd and this detail route.
|
|
222
|
+
kind: z.enum(["meeting", "comm", "doc", "personal"]),
|
|
221
223
|
title: z.string(),
|
|
222
224
|
content: z.string(),
|
|
223
225
|
/**
|
|
@@ -79,6 +79,7 @@ export const openApiRoutes = {
|
|
|
79
79
|
'/api/me/feed': ['GET'],
|
|
80
80
|
'/api/me/feed/read': ['POST'],
|
|
81
81
|
'/api/me/feed/read-all': ['POST'],
|
|
82
|
+
'/api/me/md': ['GET'],
|
|
82
83
|
'/api/me/meetings/recordings/start': ['POST'],
|
|
83
84
|
'/api/me/meetings/recordings/{id}': ['DELETE'],
|
|
84
85
|
'/api/me/meetings/recordings/{id}/complete': ['POST'],
|
|
@@ -113,9 +113,12 @@ export const AclListResponseSchema = z.object({
|
|
|
113
113
|
* Who knows this entity EXISTS — a SIBLING of `visibility`, not a modifier
|
|
114
114
|
* on it (ADR-CONTRACTS-093).
|
|
115
115
|
*
|
|
116
|
-
* ABSENT =>
|
|
117
|
-
* meeting_recording
|
|
118
|
-
*
|
|
116
|
+
* ABSENT => the share dialog has no discoverability control for this
|
|
117
|
+
* entity: meeting_recording (no axis at all) and company_md (the policy is
|
|
118
|
+
* DERIVED server-side — 'org' for structural docs, ADR-BE-482 — never
|
|
119
|
+
* share-governed). PRESENT only for work_item, the one stored,
|
|
120
|
+
* share-governed axis (ADR-BE-486). Deliberately not defaulted: `org|owners`
|
|
121
|
+
* has no honest cross-entity value, unlike `general_access_role`'s `viewer`.
|
|
119
122
|
*
|
|
120
123
|
* The policy's domain is NON-READERS only. When `visibility` is `org` every
|
|
121
124
|
* member is a reader, so the stored value affects no one — it is not erased,
|
package/src/resource-keys.ts
CHANGED
|
@@ -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
|
|