@hexis-ai/engram-server 0.11.3 → 0.13.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/dist/adapters/memory-key-store.d.ts +4 -0
- package/dist/adapters/memory-key-store.js +12 -0
- package/dist/adapters/memory.js +47 -66
- package/dist/adapters/pg-tagged.d.ts +18 -0
- package/dist/adapters/pg-tagged.js +29 -0
- package/dist/adapters/postgres-key-store.d.ts +4 -0
- package/dist/adapters/postgres-key-store.js +14 -3
- package/dist/adapters/postgres-org-store.d.ts +42 -0
- package/dist/adapters/postgres-org-store.js +120 -0
- package/dist/adapters/postgres.js +57 -80
- package/dist/adapters/util.d.ts +27 -0
- package/dist/adapters/util.js +47 -0
- package/dist/admin.d.ts +26 -4
- package/dist/admin.js +126 -7
- package/dist/auth-resolver.d.ts +32 -0
- package/dist/auth-resolver.js +53 -0
- package/dist/auth.d.ts +196 -0
- package/dist/auth.js +164 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/key-store.d.ts +5 -0
- package/dist/main.js +84 -26
- package/dist/migrations/0006-auth.d.ts +2 -0
- package/dist/migrations/0006-auth.js +84 -0
- package/dist/migrations/0007-orgs.d.ts +2 -0
- package/dist/migrations/0007-orgs.js +59 -0
- package/dist/migrations/index.js +4 -0
- package/dist/openapi.js +340 -3
- package/dist/org-store.d.ts +73 -0
- package/dist/org-store.js +12 -0
- package/dist/routes/orgs.d.ts +27 -0
- package/dist/routes/orgs.js +185 -0
- package/dist/schemas.d.ts +18 -0
- package/dist/schemas.js +19 -0
- package/dist/server.d.ts +39 -0
- package/dist/server.js +85 -7
- package/dist/services/orgs.d.ts +95 -0
- package/dist/services/orgs.js +159 -0
- package/dist/storage.d.ts +6 -0
- package/dist/storage.js +14 -0
- package/openapi.json +1279 -1
- package/package.json +5 -11
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const name = "0007-orgs";
|
|
2
|
+
export const sql = `
|
|
3
|
+
-- ============================================================
|
|
4
|
+
-- Wave G: orgs as the top-level grouping (Langfuse-style)
|
|
5
|
+
--
|
|
6
|
+
-- Before: cookie-auth users were members of individual workspaces
|
|
7
|
+
-- (engram_workspace_members). That doesn't scale when a single
|
|
8
|
+
-- dev team operates many tenant workspaces — every new tenant
|
|
9
|
+
-- meant re-adding every dev. Move membership up one level so a
|
|
10
|
+
-- user joins an org once and sees every workspace under it.
|
|
11
|
+
--
|
|
12
|
+
-- New shape:
|
|
13
|
+
-- engram_orgs top-level grouping
|
|
14
|
+
-- engram_workspaces now has org_id (NULL allowed only for
|
|
15
|
+
-- tests / legacy api-key-only rows;
|
|
16
|
+
-- cookie-auth requires the workspace's
|
|
17
|
+
-- org membership)
|
|
18
|
+
-- engram_org_members replaces engram_workspace_members
|
|
19
|
+
-- ============================================================
|
|
20
|
+
|
|
21
|
+
CREATE TABLE IF NOT EXISTS engram_orgs (
|
|
22
|
+
id TEXT PRIMARY KEY,
|
|
23
|
+
name TEXT,
|
|
24
|
+
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
25
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
ALTER TABLE engram_workspaces ADD COLUMN IF NOT EXISTS org_id TEXT;
|
|
29
|
+
|
|
30
|
+
DO $$
|
|
31
|
+
BEGIN
|
|
32
|
+
IF NOT EXISTS (
|
|
33
|
+
SELECT 1 FROM pg_constraint
|
|
34
|
+
WHERE conname = 'engram_workspaces_org_id_fkey'
|
|
35
|
+
) THEN
|
|
36
|
+
ALTER TABLE engram_workspaces
|
|
37
|
+
ADD CONSTRAINT engram_workspaces_org_id_fkey
|
|
38
|
+
FOREIGN KEY (org_id) REFERENCES engram_orgs(id) ON DELETE CASCADE;
|
|
39
|
+
END IF;
|
|
40
|
+
END $$;
|
|
41
|
+
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_engram_workspaces_org
|
|
43
|
+
ON engram_workspaces (org_id);
|
|
44
|
+
|
|
45
|
+
CREATE TABLE IF NOT EXISTS engram_org_members (
|
|
46
|
+
org_id TEXT NOT NULL REFERENCES engram_orgs(id) ON DELETE CASCADE,
|
|
47
|
+
user_id TEXT NOT NULL REFERENCES engram_auth_users(id) ON DELETE CASCADE,
|
|
48
|
+
role TEXT NOT NULL DEFAULT 'member',
|
|
49
|
+
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
50
|
+
PRIMARY KEY (org_id, user_id)
|
|
51
|
+
);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_engram_org_members_user
|
|
53
|
+
ON engram_org_members (user_id);
|
|
54
|
+
|
|
55
|
+
-- workspace_members from 0006-auth is obsolete in the org-based
|
|
56
|
+
-- model. Drop it; the only path to workspace access is via org
|
|
57
|
+
-- membership.
|
|
58
|
+
DROP TABLE IF EXISTS engram_workspace_members;
|
|
59
|
+
`;
|
package/dist/migrations/index.js
CHANGED
|
@@ -3,6 +3,8 @@ import * as m0002 from "./0002-aliases";
|
|
|
3
3
|
import * as m0003 from "./0003-identities";
|
|
4
4
|
import * as m0004 from "./0004-schema-completion";
|
|
5
5
|
import * as m0005 from "./0005-session-updated-at";
|
|
6
|
+
import * as m0006 from "./0006-auth";
|
|
7
|
+
import * as m0007 from "./0007-orgs";
|
|
6
8
|
/**
|
|
7
9
|
* Schema migrations, applied in array order. Add a new file under
|
|
8
10
|
* `migrations/NNNN-<slug>.ts` exporting `name` and `sql`, then append it
|
|
@@ -16,4 +18,6 @@ export const MIGRATIONS = [
|
|
|
16
18
|
{ name: m0003.name, sql: m0003.sql },
|
|
17
19
|
{ name: m0004.name, sql: m0004.sql },
|
|
18
20
|
{ name: m0005.name, sql: m0005.sql },
|
|
21
|
+
{ name: m0006.name, sql: m0006.sql },
|
|
22
|
+
{ name: m0007.name, sql: m0007.sql },
|
|
19
23
|
];
|
package/dist/openapi.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* codes stay as-is.
|
|
19
19
|
*/
|
|
20
20
|
import { z } from "zod";
|
|
21
|
-
import { createWorkspaceSchema, eventBatchSchema, issueKeySchema, personCreateSchema, personUpdateSchema, searchRequestSchema, sessionInitSchema, } from "./schemas";
|
|
21
|
+
import { addMemberSchema, createOrgSchema, createWorkspaceSchema, eventBatchSchema, issueKeySchema, orgPatchSchema, personCreateSchema, personUpdateSchema, searchRequestSchema, sessionInitSchema, sessionUpdateSchema, aliasUpsertSchema, identityUpsertSchema, workspacePatchSchema, } from "./schemas";
|
|
22
22
|
/** Convert a Zod schema to a JSON Schema object for an OpenAPI component. */
|
|
23
23
|
function toComponent(schema) {
|
|
24
24
|
const js = z.toJSONSchema(schema);
|
|
@@ -37,7 +37,7 @@ const pathParam = (name, description) => ({
|
|
|
37
37
|
schema: { type: "string" },
|
|
38
38
|
description,
|
|
39
39
|
});
|
|
40
|
-
const queryParam = (name, description, schema = { type: "string" }) => ({ name, in: "query", required
|
|
40
|
+
const queryParam = (name, description, schema = { type: "string" }, required = false) => ({ name, in: "query", required, schema, description });
|
|
41
41
|
const res = (description) => ({ description });
|
|
42
42
|
/** Default security: a workspace API key. `/admin/v1` paths override this. */
|
|
43
43
|
const workspaceAuth = [{ workspaceKey: [] }];
|
|
@@ -54,14 +54,23 @@ const adminAuth = [{ adminToken: [] }];
|
|
|
54
54
|
// ---------------------------------------------------------------------
|
|
55
55
|
/** Every tag, in sidebar order, with a Japanese description. */
|
|
56
56
|
const TAG_DEFS = [
|
|
57
|
-
{ name: "Me", description: "
|
|
57
|
+
{ name: "Me", description: "識別プローブ・閲覧可能な workspace/org 一覧" },
|
|
58
58
|
{ name: "Sessions", description: "セッションの作成・取得・イベント追加" },
|
|
59
59
|
{ name: "Search", description: "ワークスペースコーパスへのスコアリング検索" },
|
|
60
60
|
{ name: "Persons", description: "person の作成・更新・検索" },
|
|
61
|
+
{ name: "Aliases", description: "ワークスペース横断の alias 解決" },
|
|
61
62
|
{
|
|
62
63
|
name: "Identities",
|
|
63
64
|
description: "外部 ID(slack: / email: など)の resolve と upsert",
|
|
64
65
|
},
|
|
66
|
+
{
|
|
67
|
+
name: "Orgs (self-serve)",
|
|
68
|
+
description: "engram-web からの cookie 認証 + org-member ロールでの org / workspace / API キー管理",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "Orgs (admin)",
|
|
72
|
+
description: "プラットフォーム管理者トークンでの org 管理",
|
|
73
|
+
},
|
|
65
74
|
{
|
|
66
75
|
name: "Workspaces (admin)",
|
|
67
76
|
description: "ワークスペースの管理(管理者トークン必須)",
|
|
@@ -99,6 +108,24 @@ function buildPaths() {
|
|
|
99
108
|
},
|
|
100
109
|
},
|
|
101
110
|
}),
|
|
111
|
+
"/v1/me/workspaces": tagged("Me", {
|
|
112
|
+
get: {
|
|
113
|
+
summary: "サインイン中のユーザーが閲覧できる workspace 一覧。cookie 認証専用 — workspace 選択前にも到達できるよう workspace ゲートの前段に置かれている。",
|
|
114
|
+
responses: {
|
|
115
|
+
"200": res("workspace 一覧"),
|
|
116
|
+
"401": res("認証エラー"),
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
}),
|
|
120
|
+
"/v1/me/orgs": tagged("Me", {
|
|
121
|
+
get: {
|
|
122
|
+
summary: "サインイン中のユーザーが member として所属する org 一覧(自分の role 付き)。cookie 認証専用。",
|
|
123
|
+
responses: {
|
|
124
|
+
"200": res("org 一覧"),
|
|
125
|
+
"401": res("認証エラー"),
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
102
129
|
"/v1/sessions": tagged("Sessions", {
|
|
103
130
|
post: {
|
|
104
131
|
summary: "セッションを作成する。",
|
|
@@ -282,6 +309,16 @@ function buildPaths() {
|
|
|
282
309
|
},
|
|
283
310
|
},
|
|
284
311
|
}),
|
|
312
|
+
"/v1/aliases": tagged("Aliases", {
|
|
313
|
+
get: {
|
|
314
|
+
summary: "ワークスペース全体で alias 名を case-insensitive に逆引き。同名の alias を持つ複数の person を `last_used` desc で返す。`persons` map も同梱。",
|
|
315
|
+
parameters: [queryParam("name", "alias 名(URL-encoded)。", { type: "string" }, true)],
|
|
316
|
+
responses: {
|
|
317
|
+
"200": res("alias 一覧 + persons map"),
|
|
318
|
+
"401": res("認証エラー"),
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
}),
|
|
285
322
|
"/v1/identities/{ref}": tagged("Identities", {
|
|
286
323
|
put: {
|
|
287
324
|
summary: "ref(例: `slack:U12345`、`email:foo@bar.com`)で identity を upsert する。",
|
|
@@ -304,6 +341,299 @@ function buildPaths() {
|
|
|
304
341
|
},
|
|
305
342
|
},
|
|
306
343
|
}),
|
|
344
|
+
// ------------------------------------------------------------------
|
|
345
|
+
// Self-service org surface (Wave G5). Cookie auth + org membership.
|
|
346
|
+
// engram-web の settings UI から呼ばれる。/admin/v1/orgs/* と同じ
|
|
347
|
+
// 操作を「自分が所属する org に絞って」叩けるエンドポイント群。
|
|
348
|
+
// ------------------------------------------------------------------
|
|
349
|
+
"/v1/orgs/{id}": tagged("Orgs (self-serve)", {
|
|
350
|
+
get: {
|
|
351
|
+
summary: "自分が member の org を取得する(自分の role 付き)。",
|
|
352
|
+
parameters: [pathParam("id", "org id。")],
|
|
353
|
+
responses: {
|
|
354
|
+
"200": res("org + role"),
|
|
355
|
+
"403": res("この org の member ではない"),
|
|
356
|
+
"404": res("org が見つからない"),
|
|
357
|
+
"401": res("認証エラー"),
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
patch: {
|
|
361
|
+
summary: "org の name / metadata を更新する(owner / admin のみ)。",
|
|
362
|
+
parameters: [pathParam("id", "org id。")],
|
|
363
|
+
requestBody: jsonBody("OrgPatch"),
|
|
364
|
+
responses: {
|
|
365
|
+
"200": res("更新後の org"),
|
|
366
|
+
"403": res("権限不足(owner / admin が必要)"),
|
|
367
|
+
"404": res("org が見つからない"),
|
|
368
|
+
"401": res("認証エラー"),
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
}),
|
|
372
|
+
"/v1/orgs/{id}/members": tagged("Orgs (self-serve)", {
|
|
373
|
+
get: {
|
|
374
|
+
summary: "org の member 一覧。",
|
|
375
|
+
parameters: [pathParam("id", "org id。")],
|
|
376
|
+
responses: {
|
|
377
|
+
"200": res("member 一覧"),
|
|
378
|
+
"403": res("この org の member ではない"),
|
|
379
|
+
"401": res("認証エラー"),
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
post: {
|
|
383
|
+
summary: "member を追加する(owner / admin のみ)。`email` か `userId` のいずれかを必須で渡す。",
|
|
384
|
+
parameters: [pathParam("id", "org id。")],
|
|
385
|
+
requestBody: jsonBody("AddMember"),
|
|
386
|
+
responses: {
|
|
387
|
+
"200": res("追加された membership"),
|
|
388
|
+
"400": res("`userId` も `email` も無い、または body が不正"),
|
|
389
|
+
"403": res("権限不足"),
|
|
390
|
+
"404": res("email から user を解決できない"),
|
|
391
|
+
"401": res("認証エラー"),
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
}),
|
|
395
|
+
"/v1/orgs/{id}/members/{userId}": tagged("Orgs (self-serve)", {
|
|
396
|
+
delete: {
|
|
397
|
+
summary: "member を削除する(owner / admin のみ)。最後の owner は削除拒否(`cannot_remove_last_owner`)。",
|
|
398
|
+
parameters: [pathParam("id", "org id。"), pathParam("userId", "user id。")],
|
|
399
|
+
responses: {
|
|
400
|
+
"204": res("削除完了"),
|
|
401
|
+
"400": res("最後の owner は削除できない"),
|
|
402
|
+
"403": res("権限不足"),
|
|
403
|
+
"401": res("認証エラー"),
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
}),
|
|
407
|
+
"/v1/orgs/{id}/workspaces": tagged("Orgs (self-serve)", {
|
|
408
|
+
get: {
|
|
409
|
+
summary: "org の workspace 一覧。",
|
|
410
|
+
parameters: [pathParam("id", "org id。")],
|
|
411
|
+
responses: {
|
|
412
|
+
"200": res("workspace 一覧"),
|
|
413
|
+
"403": res("この org の member ではない"),
|
|
414
|
+
"401": res("認証エラー"),
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
post: {
|
|
418
|
+
summary: "workspace を作成し、(issueKey=false でなければ)初期 API キーも発行する。owner / admin のみ。",
|
|
419
|
+
parameters: [pathParam("id", "org id。")],
|
|
420
|
+
requestBody: jsonBody("CreateWorkspace"),
|
|
421
|
+
responses: {
|
|
422
|
+
"200": res("workspace(issueKey=false でない限り key も含む)"),
|
|
423
|
+
"400": res("body または workspace id が不正"),
|
|
424
|
+
"403": res("権限不足"),
|
|
425
|
+
"401": res("認証エラー"),
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
}),
|
|
429
|
+
"/v1/orgs/{id}/workspaces/{wsId}": tagged("Orgs (self-serve)", {
|
|
430
|
+
patch: {
|
|
431
|
+
summary: "workspace の name / metadata を更新する(owner / admin のみ)。",
|
|
432
|
+
parameters: [pathParam("id", "org id。"), pathParam("wsId", "workspace id。")],
|
|
433
|
+
requestBody: jsonBody("WorkspacePatch"),
|
|
434
|
+
responses: {
|
|
435
|
+
"200": res("更新後の workspace"),
|
|
436
|
+
"403": res("権限不足"),
|
|
437
|
+
"404": res("workspace がこの org に属さない / 存在しない"),
|
|
438
|
+
"401": res("認証エラー"),
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
delete: {
|
|
442
|
+
summary: "workspace を削除する(セッション・persons・identities・API キーにカスケード)。owner / admin のみ。",
|
|
443
|
+
parameters: [pathParam("id", "org id。"), pathParam("wsId", "workspace id。")],
|
|
444
|
+
responses: {
|
|
445
|
+
"204": res("削除完了"),
|
|
446
|
+
"403": res("権限不足"),
|
|
447
|
+
"404": res("workspace がこの org に属さない"),
|
|
448
|
+
"401": res("認証エラー"),
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
}),
|
|
452
|
+
"/v1/orgs/{id}/workspaces/{wsId}/keys": tagged("Orgs (self-serve)", {
|
|
453
|
+
get: {
|
|
454
|
+
summary: "workspace の API キー一覧(owner / admin のみ)。",
|
|
455
|
+
parameters: [pathParam("id", "org id。"), pathParam("wsId", "workspace id。")],
|
|
456
|
+
responses: {
|
|
457
|
+
"200": res("キー一覧"),
|
|
458
|
+
"403": res("権限不足"),
|
|
459
|
+
"404": res("workspace がこの org に属さない"),
|
|
460
|
+
"401": res("認証エラー"),
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
post: {
|
|
464
|
+
summary: "新しい API キーを発行する(owner / admin のみ)。",
|
|
465
|
+
parameters: [pathParam("id", "org id。"), pathParam("wsId", "workspace id。")],
|
|
466
|
+
requestBody: jsonBody("IssueKey", false),
|
|
467
|
+
responses: {
|
|
468
|
+
"200": res("発行されたキー(raw は一度のみ返却)"),
|
|
469
|
+
"403": res("権限不足"),
|
|
470
|
+
"404": res("workspace がこの org に属さない"),
|
|
471
|
+
"401": res("認証エラー"),
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
}),
|
|
475
|
+
"/v1/orgs/{id}/workspaces/{wsId}/keys/{keyId}": tagged("Orgs (self-serve)", {
|
|
476
|
+
delete: {
|
|
477
|
+
summary: "API キーを無効化する(owner / admin のみ)。",
|
|
478
|
+
parameters: [
|
|
479
|
+
pathParam("id", "org id。"),
|
|
480
|
+
pathParam("wsId", "workspace id。"),
|
|
481
|
+
pathParam("keyId", "key id。"),
|
|
482
|
+
],
|
|
483
|
+
responses: {
|
|
484
|
+
"204": res("無効化完了"),
|
|
485
|
+
"403": res("権限不足"),
|
|
486
|
+
"404": res("workspace / key がこの org に属さない、または存在しない"),
|
|
487
|
+
"401": res("認証エラー"),
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
}),
|
|
491
|
+
// ------------------------------------------------------------------
|
|
492
|
+
// Admin org surface (Wave G1). Platform admin token.
|
|
493
|
+
// ------------------------------------------------------------------
|
|
494
|
+
"/admin/v1/orgs": tagged("Orgs (admin)", {
|
|
495
|
+
post: {
|
|
496
|
+
summary: "org を作成する。",
|
|
497
|
+
security: adminAuth,
|
|
498
|
+
requestBody: jsonBody("CreateOrg"),
|
|
499
|
+
responses: {
|
|
500
|
+
"200": res("作成された org"),
|
|
501
|
+
"400": res("body が不正"),
|
|
502
|
+
"401": res("認証エラー"),
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
get: {
|
|
506
|
+
summary: "全 org を一覧取得する。",
|
|
507
|
+
security: adminAuth,
|
|
508
|
+
responses: {
|
|
509
|
+
"200": res("org 一覧"),
|
|
510
|
+
"401": res("認証エラー"),
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
}),
|
|
514
|
+
"/admin/v1/orgs/{id}": tagged("Orgs (admin)", {
|
|
515
|
+
get: {
|
|
516
|
+
summary: "単一の org を取得する。",
|
|
517
|
+
security: adminAuth,
|
|
518
|
+
parameters: [pathParam("id", "org id。")],
|
|
519
|
+
responses: {
|
|
520
|
+
"200": res("org"),
|
|
521
|
+
"404": res("org が見つからない"),
|
|
522
|
+
"401": res("認証エラー"),
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
delete: {
|
|
526
|
+
summary: "org を削除する(member と workspace にカスケード)。",
|
|
527
|
+
security: adminAuth,
|
|
528
|
+
parameters: [pathParam("id", "org id。")],
|
|
529
|
+
responses: {
|
|
530
|
+
"204": res("削除完了"),
|
|
531
|
+
"404": res("org が見つからない"),
|
|
532
|
+
"401": res("認証エラー"),
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
}),
|
|
536
|
+
"/admin/v1/orgs/{id}/members": tagged("Orgs (admin)", {
|
|
537
|
+
get: {
|
|
538
|
+
summary: "org の member 一覧。",
|
|
539
|
+
security: adminAuth,
|
|
540
|
+
parameters: [pathParam("id", "org id。")],
|
|
541
|
+
responses: {
|
|
542
|
+
"200": res("member 一覧"),
|
|
543
|
+
"404": res("org が見つからない"),
|
|
544
|
+
"401": res("認証エラー"),
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
post: {
|
|
548
|
+
summary: "member を追加する(email または userId)。",
|
|
549
|
+
security: adminAuth,
|
|
550
|
+
parameters: [pathParam("id", "org id。")],
|
|
551
|
+
requestBody: jsonBody("AddMember"),
|
|
552
|
+
responses: {
|
|
553
|
+
"200": res("追加された membership"),
|
|
554
|
+
"400": res("`userId` も `email` も無い、または body が不正"),
|
|
555
|
+
"404": res("org または email から user を解決できない"),
|
|
556
|
+
"401": res("認証エラー"),
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
}),
|
|
560
|
+
"/admin/v1/orgs/{id}/members/{userId}": tagged("Orgs (admin)", {
|
|
561
|
+
delete: {
|
|
562
|
+
summary: "member を削除する。最後の owner は削除拒否(`cannot_remove_last_owner`)。",
|
|
563
|
+
security: adminAuth,
|
|
564
|
+
parameters: [pathParam("id", "org id。"), pathParam("userId", "user id。")],
|
|
565
|
+
responses: {
|
|
566
|
+
"204": res("削除完了"),
|
|
567
|
+
"400": res("最後の owner は削除できない"),
|
|
568
|
+
"404": res("org が見つからない"),
|
|
569
|
+
"401": res("認証エラー"),
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
}),
|
|
573
|
+
"/admin/v1/orgs/{id}/workspaces": tagged("Orgs (admin)", {
|
|
574
|
+
post: {
|
|
575
|
+
summary: "org の下に workspace を作成し、(issueKey=false でなければ)初期 API キーも発行する。",
|
|
576
|
+
security: adminAuth,
|
|
577
|
+
parameters: [pathParam("id", "org id。")],
|
|
578
|
+
requestBody: jsonBody("CreateWorkspace"),
|
|
579
|
+
responses: {
|
|
580
|
+
"200": res("workspace(issueKey=false でない限り key も含む)"),
|
|
581
|
+
"400": res("body または workspace id が不正"),
|
|
582
|
+
"404": res("org が見つからない"),
|
|
583
|
+
"401": res("認証エラー"),
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
get: {
|
|
587
|
+
summary: "org の workspace 一覧。",
|
|
588
|
+
security: adminAuth,
|
|
589
|
+
parameters: [pathParam("id", "org id。")],
|
|
590
|
+
responses: {
|
|
591
|
+
"200": res("workspace 一覧"),
|
|
592
|
+
"404": res("org が見つからない"),
|
|
593
|
+
"401": res("認証エラー"),
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
}),
|
|
597
|
+
"/admin/v1/orgs/{id}/workspaces/{wsId}/keys": tagged("Orgs (admin)", {
|
|
598
|
+
get: {
|
|
599
|
+
summary: "org 配下の workspace の API キー一覧(ハッシュのみ)。",
|
|
600
|
+
security: adminAuth,
|
|
601
|
+
parameters: [pathParam("id", "org id。"), pathParam("wsId", "workspace id。")],
|
|
602
|
+
responses: {
|
|
603
|
+
"200": res("キー一覧"),
|
|
604
|
+
"404": res("workspace がこの org に属さない / org が無い"),
|
|
605
|
+
"401": res("認証エラー"),
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
post: {
|
|
609
|
+
summary: "org 配下の workspace に新しい API キーを発行する。`createWorkspaceUnderOrg` 後のキー rotation に使う。",
|
|
610
|
+
security: adminAuth,
|
|
611
|
+
parameters: [pathParam("id", "org id。"), pathParam("wsId", "workspace id。")],
|
|
612
|
+
requestBody: jsonBody("IssueKey", false),
|
|
613
|
+
responses: {
|
|
614
|
+
"200": res("発行されたキー(raw は一度のみ返却)"),
|
|
615
|
+
"400": res("リクエストボディが不正"),
|
|
616
|
+
"404": res("workspace がこの org に属さない / org が無い"),
|
|
617
|
+
"401": res("認証エラー"),
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
}),
|
|
621
|
+
"/admin/v1/orgs/{id}/workspaces/{wsId}/keys/{keyId}": tagged("Orgs (admin)", {
|
|
622
|
+
delete: {
|
|
623
|
+
summary: "org 配下の workspace の API キーを無効化する。",
|
|
624
|
+
security: adminAuth,
|
|
625
|
+
parameters: [
|
|
626
|
+
pathParam("id", "org id。"),
|
|
627
|
+
pathParam("wsId", "workspace id。"),
|
|
628
|
+
pathParam("keyId", "key id。"),
|
|
629
|
+
],
|
|
630
|
+
responses: {
|
|
631
|
+
"204": res("無効化完了"),
|
|
632
|
+
"404": res("workspace / key がこの org に属さない、または存在しない"),
|
|
633
|
+
"401": res("認証エラー"),
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
}),
|
|
307
637
|
"/admin/v1/workspaces": tagged("Workspaces (admin)", {
|
|
308
638
|
post: {
|
|
309
639
|
summary: "ワークスペースを作成する(デフォルトで初期キーも発行する)。",
|
|
@@ -425,15 +755,22 @@ export function buildOpenApiDocument() {
|
|
|
425
755
|
},
|
|
426
756
|
schemas: {
|
|
427
757
|
SessionInit: toComponent(sessionInitSchema),
|
|
758
|
+
SessionUpdate: toComponent(sessionUpdateSchema),
|
|
428
759
|
// `EventBatch` inlines the per-event shape. A standalone `SessionEvent`
|
|
429
760
|
// component (cross-`$ref`'d) is a planned follow-up alongside response
|
|
430
761
|
// schemas — zod's `toJSONSchema` inlines by default.
|
|
431
762
|
EventBatch: toComponent(eventBatchSchema),
|
|
432
763
|
PersonCreate: toComponent(personCreateSchema),
|
|
433
764
|
PersonUpdate: toComponent(personUpdateSchema),
|
|
765
|
+
AliasUpsert: toComponent(aliasUpsertSchema),
|
|
766
|
+
IdentityUpsert: toComponent(identityUpsertSchema),
|
|
434
767
|
SearchRequest: toComponent(searchRequestSchema),
|
|
435
768
|
CreateWorkspace: toComponent(createWorkspaceSchema),
|
|
436
769
|
IssueKey: toComponent(issueKeySchema),
|
|
770
|
+
CreateOrg: toComponent(createOrgSchema),
|
|
771
|
+
OrgPatch: toComponent(orgPatchSchema),
|
|
772
|
+
WorkspacePatch: toComponent(workspacePatchSchema),
|
|
773
|
+
AddMember: toComponent(addMemberSchema),
|
|
437
774
|
},
|
|
438
775
|
},
|
|
439
776
|
paths: buildPaths(),
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Org / org-member / workspace-under-org persistence.
|
|
3
|
+
*
|
|
4
|
+
* Orgs are the top-level grouping in the cookie-auth model: a user
|
|
5
|
+
* joins an org once and gains access to every workspace owned by
|
|
6
|
+
* that org. Workspaces still exist as the data-isolation unit
|
|
7
|
+
* (sessions / persons / aliases / identities are per-workspace).
|
|
8
|
+
*
|
|
9
|
+
* Machine api-keys (api-key auth) are unaware of orgs — they
|
|
10
|
+
* continue to resolve directly to a workspace.
|
|
11
|
+
*/
|
|
12
|
+
export interface OrgRow {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string | null;
|
|
15
|
+
metadata: Record<string, unknown>;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
}
|
|
18
|
+
export interface OrgMembershipRow {
|
|
19
|
+
orgId: string;
|
|
20
|
+
userId: string;
|
|
21
|
+
role: string;
|
|
22
|
+
joinedAt: string;
|
|
23
|
+
}
|
|
24
|
+
export interface OrgStore {
|
|
25
|
+
createOrg(input: {
|
|
26
|
+
id?: string;
|
|
27
|
+
name?: string;
|
|
28
|
+
metadata?: Record<string, unknown>;
|
|
29
|
+
}): Promise<OrgRow>;
|
|
30
|
+
getOrg(id: string): Promise<OrgRow | null>;
|
|
31
|
+
listOrgs(): Promise<OrgRow[]>;
|
|
32
|
+
updateOrg(id: string, patch: {
|
|
33
|
+
name?: string;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}): Promise<OrgRow>;
|
|
36
|
+
deleteOrg(id: string): Promise<void>;
|
|
37
|
+
listMembers(orgId: string): Promise<OrgMembershipRow[]>;
|
|
38
|
+
upsertMember(input: {
|
|
39
|
+
orgId: string;
|
|
40
|
+
userId: string;
|
|
41
|
+
role?: string;
|
|
42
|
+
}): Promise<OrgMembershipRow>;
|
|
43
|
+
removeMember(orgId: string, userId: string): Promise<void>;
|
|
44
|
+
/** Look up a better-auth user by primary email. */
|
|
45
|
+
findUserByEmail(email: string): Promise<{
|
|
46
|
+
id: string;
|
|
47
|
+
email: string;
|
|
48
|
+
} | null>;
|
|
49
|
+
/** Orgs the given user is a member of. */
|
|
50
|
+
listOrgsForUser(userId: string): Promise<OrgMembershipRow[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Set the org an already-existing workspace belongs to. Called
|
|
53
|
+
* by the admin endpoint that creates a workspace under an org
|
|
54
|
+
* (key issuance and engram_workspaces.org_id are written together).
|
|
55
|
+
*/
|
|
56
|
+
setWorkspaceOrg(workspaceId: string, orgId: string): Promise<void>;
|
|
57
|
+
/** Workspaces in the given org. */
|
|
58
|
+
listWorkspacesForOrg(orgId: string): Promise<{
|
|
59
|
+
id: string;
|
|
60
|
+
name: string | null;
|
|
61
|
+
}[]>;
|
|
62
|
+
/** Workspaces visible to the user, aggregated across all their orgs. */
|
|
63
|
+
listWorkspacesForUser(userId: string): Promise<{
|
|
64
|
+
id: string;
|
|
65
|
+
name: string | null;
|
|
66
|
+
orgId: string;
|
|
67
|
+
}[]>;
|
|
68
|
+
/** Returns true if the user is an org-member of the workspace's org. */
|
|
69
|
+
userCanAccessWorkspace(userId: string, workspaceId: string): Promise<boolean>;
|
|
70
|
+
/** Returns true if the workspace's `org_id` matches. Admin-side check
|
|
71
|
+
* with no user context. */
|
|
72
|
+
workspaceInOrg(orgId: string, workspaceId: string): Promise<boolean>;
|
|
73
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Org / org-member / workspace-under-org persistence.
|
|
3
|
+
*
|
|
4
|
+
* Orgs are the top-level grouping in the cookie-auth model: a user
|
|
5
|
+
* joins an org once and gains access to every workspace owned by
|
|
6
|
+
* that org. Workspaces still exist as the data-isolation unit
|
|
7
|
+
* (sessions / persons / aliases / identities are per-workspace).
|
|
8
|
+
*
|
|
9
|
+
* Machine api-keys (api-key auth) are unaware of orgs — they
|
|
10
|
+
* continue to resolve directly to a workspace.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie-auth org self-service routes (Wave G5).
|
|
3
|
+
*
|
|
4
|
+
* Auth gating only — the underlying logic lives in services/orgs.ts so
|
|
5
|
+
* the admin-token surface (`/admin/v1/orgs/*`) can call the same code
|
|
6
|
+
* without duplicating it.
|
|
7
|
+
*
|
|
8
|
+
* Mount BEFORE the workspace gate in src/server.ts; these endpoints
|
|
9
|
+
* have their own membership check and a chosen workspace would be
|
|
10
|
+
* irrelevant noise.
|
|
11
|
+
*/
|
|
12
|
+
import { Hono } from "hono";
|
|
13
|
+
import type { EngramAuth } from "../auth";
|
|
14
|
+
import type { KeyStore } from "../key-store";
|
|
15
|
+
import type { OrgStore } from "../org-store";
|
|
16
|
+
export interface OrgsRouteOptions {
|
|
17
|
+
authHandler: EngramAuth;
|
|
18
|
+
orgStore: OrgStore;
|
|
19
|
+
keyStore: KeyStore;
|
|
20
|
+
}
|
|
21
|
+
type Env = {
|
|
22
|
+
Variables: {
|
|
23
|
+
request_id: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export declare function orgsRoutes(opts: OrgsRouteOptions): Hono<Env>;
|
|
27
|
+
export {};
|