@hexis-ai/engram-server 0.12.0 → 0.14.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 +13 -0
- package/dist/adapters/memory-key-store.js +27 -0
- package/dist/adapters/memory.js +53 -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 +5 -0
- package/dist/adapters/postgres-key-store.js +21 -5
- package/dist/adapters/postgres-org-store.d.ts +5 -1
- package/dist/adapters/postgres-org-store.js +25 -8
- package/dist/adapters/postgres.js +76 -83
- package/dist/adapters/util.d.ts +27 -0
- package/dist/adapters/util.js +47 -0
- package/dist/admin.js +78 -89
- package/dist/key-store.d.ts +13 -0
- package/dist/main.js +29 -44
- package/dist/migrations/0008-trigger-metadata.d.ts +2 -0
- package/dist/migrations/0008-trigger-metadata.js +15 -0
- package/dist/migrations/index.js +2 -0
- package/dist/openapi.js +340 -3
- package/dist/org-store.d.ts +7 -6
- package/dist/routes/orgs.d.ts +27 -0
- package/dist/routes/orgs.js +185 -0
- package/dist/schemas.d.ts +22 -0
- package/dist/schemas.js +23 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.js +40 -23
- package/dist/services/orgs.d.ts +99 -0
- package/dist/services/orgs.js +163 -0
- package/dist/storage.d.ts +8 -0
- package/dist/storage.js +20 -0
- package/openapi.json +1331 -13
- package/package.json +4 -13
package/dist/main.js
CHANGED
|
@@ -1,32 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Production entrypoint.
|
|
3
|
-
*
|
|
4
|
-
* Required env:
|
|
5
|
-
* ENGRAM_ADMIN_TOKEN platform-level bearer for `/admin/v1/*`. Treat as a
|
|
6
|
-
* root credential — anyone with it can mint workspaces
|
|
7
|
-
* and API keys.
|
|
8
|
-
*
|
|
9
|
-
* Optional env:
|
|
10
|
-
* PORT default 8080
|
|
11
|
-
* DATABASE_URL if unset, falls back to InMemoryKeyStore +
|
|
12
|
-
* InMemoryAdapter (NOT durable across restarts)
|
|
13
|
-
* DATABASE_SOCKET_PATH Cloud SQL Auth Proxy unix socket dir
|
|
14
|
-
*
|
|
15
|
-
* Optional env (engram-web cookie auth — see ./auth.ts for the full set):
|
|
16
|
-
* ENGRAM_AUTH_SECRET, ENGRAM_AUTH_URL,
|
|
17
|
-
* ENGRAM_AUTH_GOOGLE_ID, ENGRAM_AUTH_GOOGLE_SECRET,
|
|
18
|
-
* ENGRAM_AUTH_COOKIE_DOMAIN, ENGRAM_AUTH_TRUSTED_ORIGINS,
|
|
19
|
-
* ENGRAM_AUTH_EMAIL_DOMAIN
|
|
20
|
-
*
|
|
21
|
-
* Workspaces and their API keys are provisioned exclusively through the
|
|
22
|
-
* admin API. There is no single-tenant fallback — every caller must hold a
|
|
23
|
-
* workspace-scoped key issued by `POST /admin/v1/workspaces`.
|
|
24
|
-
*/
|
|
25
1
|
import { createServer } from "./server";
|
|
26
2
|
import { InMemoryAdapter } from "./adapters/memory";
|
|
27
3
|
import { PostgresAdapter } from "./adapters/postgres";
|
|
28
4
|
import { InMemoryKeyStore } from "./adapters/memory-key-store";
|
|
29
5
|
import { PostgresKeyStore } from "./adapters/postgres-key-store";
|
|
6
|
+
import { pgSqlClient } from "./adapters/pg-tagged";
|
|
30
7
|
import { buildAuth } from "./auth";
|
|
31
8
|
import { makeCookieAuthResolver } from "./auth-resolver";
|
|
32
9
|
import { PostgresOrgStore } from "./adapters/postgres-org-store";
|
|
@@ -38,8 +15,9 @@ if (!ADMIN_TOKEN) {
|
|
|
38
15
|
console.error("[engram-server] ENGRAM_ADMIN_TOKEN is required");
|
|
39
16
|
process.exit(1);
|
|
40
17
|
}
|
|
41
|
-
const
|
|
42
|
-
const {
|
|
18
|
+
const pool = await buildPool();
|
|
19
|
+
const { keyStore, getStorage } = await buildStores(pool);
|
|
20
|
+
const { authHandler, cookieAuth, orgStore } = await buildCookieAuth(pool, getStorage);
|
|
43
21
|
const CORS_ORIGINS = (process.env.ENGRAM_CORS_ORIGINS ?? "")
|
|
44
22
|
.split(",")
|
|
45
23
|
.map((s) => s.trim())
|
|
@@ -54,6 +32,7 @@ const app = createServer({
|
|
|
54
32
|
...(authHandler ? { authHandler } : {}),
|
|
55
33
|
...(cookieAuth ? { cookieAuth } : {}),
|
|
56
34
|
...(orgStore ? { orgStore } : {}),
|
|
35
|
+
keyStore,
|
|
57
36
|
...(CORS_ORIGINS.length > 0 ? { corsOrigins: CORS_ORIGINS } : {}),
|
|
58
37
|
admin: {
|
|
59
38
|
token: ADMIN_TOKEN,
|
|
@@ -63,8 +42,26 @@ const app = createServer({
|
|
|
63
42
|
});
|
|
64
43
|
console.log(`[engram-server] listening on :${PORT}`);
|
|
65
44
|
export default { port: PORT, fetch: app.fetch };
|
|
66
|
-
|
|
67
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Single Postgres connection pool shared by both the engram tables
|
|
47
|
+
* (sessions/persons/keys/orgs via `pg-tagged`) and better-auth's
|
|
48
|
+
* Kysely adapter. Returns `null` in in-memory mode.
|
|
49
|
+
*/
|
|
50
|
+
async function buildPool() {
|
|
51
|
+
if (!DATABASE_URL)
|
|
52
|
+
return null;
|
|
53
|
+
const { Pool } = await import("pg");
|
|
54
|
+
const url = new URL(DATABASE_URL);
|
|
55
|
+
return new Pool({
|
|
56
|
+
host: DATABASE_SOCKET_PATH ?? url.hostname,
|
|
57
|
+
port: DATABASE_SOCKET_PATH ? undefined : Number(url.port || 5432),
|
|
58
|
+
user: decodeURIComponent(url.username),
|
|
59
|
+
password: decodeURIComponent(url.password),
|
|
60
|
+
database: url.pathname.replace(/^\//, ""),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async function buildStores(pool) {
|
|
64
|
+
if (!pool) {
|
|
68
65
|
console.warn("[engram-server] DATABASE_URL not set — in-memory mode (data is volatile)");
|
|
69
66
|
const ks = new InMemoryKeyStore();
|
|
70
67
|
const adapters = new Map();
|
|
@@ -80,10 +77,7 @@ async function buildStores() {
|
|
|
80
77
|
},
|
|
81
78
|
};
|
|
82
79
|
}
|
|
83
|
-
const
|
|
84
|
-
const sql = (DATABASE_SOCKET_PATH
|
|
85
|
-
? postgres(DATABASE_URL, { host: DATABASE_SOCKET_PATH })
|
|
86
|
-
: postgres(DATABASE_URL));
|
|
80
|
+
const sql = pgSqlClient(pool);
|
|
87
81
|
const ks = new PostgresKeyStore(sql);
|
|
88
82
|
await ks.ensureSchema();
|
|
89
83
|
// Session schema is workspace-independent — one bootstrap call is enough.
|
|
@@ -107,30 +101,21 @@ async function buildStores() {
|
|
|
107
101
|
* Google OAuth configured) — the rest of the server still works,
|
|
108
102
|
* but only api-key callers can reach /v1.
|
|
109
103
|
*/
|
|
110
|
-
async function buildCookieAuth(getStorage) {
|
|
104
|
+
async function buildCookieAuth(pool, getStorage) {
|
|
111
105
|
const secret = process.env.ENGRAM_AUTH_SECRET;
|
|
112
106
|
const baseURL = process.env.ENGRAM_AUTH_URL;
|
|
113
107
|
const googleId = process.env.ENGRAM_AUTH_GOOGLE_ID;
|
|
114
108
|
const googleSecret = process.env.ENGRAM_AUTH_GOOGLE_SECRET;
|
|
115
109
|
if (!secret || !baseURL || !googleId || !googleSecret) {
|
|
116
|
-
if (
|
|
110
|
+
if (pool) {
|
|
117
111
|
console.warn("[engram-server] cookie auth disabled (set ENGRAM_AUTH_SECRET/URL/GOOGLE_ID/GOOGLE_SECRET to enable engram-web sign-in)");
|
|
118
112
|
}
|
|
119
113
|
return {};
|
|
120
114
|
}
|
|
121
|
-
if (!
|
|
115
|
+
if (!pool) {
|
|
122
116
|
console.warn("[engram-server] cookie auth requires DATABASE_URL — skipping");
|
|
123
117
|
return {};
|
|
124
118
|
}
|
|
125
|
-
const { Pool } = await import("pg");
|
|
126
|
-
const url = new URL(DATABASE_URL);
|
|
127
|
-
const pool = new Pool({
|
|
128
|
-
host: DATABASE_SOCKET_PATH ?? url.hostname,
|
|
129
|
-
port: DATABASE_SOCKET_PATH ? undefined : Number(url.port || 5432),
|
|
130
|
-
user: decodeURIComponent(url.username),
|
|
131
|
-
password: decodeURIComponent(url.password),
|
|
132
|
-
database: url.pathname.replace(/^\//, ""),
|
|
133
|
-
});
|
|
134
119
|
const trusted = (process.env.ENGRAM_AUTH_TRUSTED_ORIGINS ?? "")
|
|
135
120
|
.split(",")
|
|
136
121
|
.map((s) => s.trim())
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const name = "0008-trigger-metadata";
|
|
2
|
+
export declare const sql = "\n-- Hosts that open a side-conversation with a stated goal (monet's\n-- start_conversation tool) need to thread two free-text fields through\n-- engram for the agent's resume flow:\n--\n-- trigger_purpose what this side-conv exists to accomplish\n-- trigger_resume_hint the condition that should resume the parent\n--\n-- Both are opaque to engram \u2014 we store and serve them verbatim. Nullable\n-- because most sessions aren't side-conversations.\n\nALTER TABLE engram_sessions ADD COLUMN IF NOT EXISTS trigger_purpose TEXT;\nALTER TABLE engram_sessions ADD COLUMN IF NOT EXISTS trigger_resume_hint TEXT;\n";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const name = "0008-trigger-metadata";
|
|
2
|
+
export const sql = `
|
|
3
|
+
-- Hosts that open a side-conversation with a stated goal (monet's
|
|
4
|
+
-- start_conversation tool) need to thread two free-text fields through
|
|
5
|
+
-- engram for the agent's resume flow:
|
|
6
|
+
--
|
|
7
|
+
-- trigger_purpose what this side-conv exists to accomplish
|
|
8
|
+
-- trigger_resume_hint the condition that should resume the parent
|
|
9
|
+
--
|
|
10
|
+
-- Both are opaque to engram — we store and serve them verbatim. Nullable
|
|
11
|
+
-- because most sessions aren't side-conversations.
|
|
12
|
+
|
|
13
|
+
ALTER TABLE engram_sessions ADD COLUMN IF NOT EXISTS trigger_purpose TEXT;
|
|
14
|
+
ALTER TABLE engram_sessions ADD COLUMN IF NOT EXISTS trigger_resume_hint TEXT;
|
|
15
|
+
`;
|
package/dist/migrations/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import * as m0004 from "./0004-schema-completion";
|
|
|
5
5
|
import * as m0005 from "./0005-session-updated-at";
|
|
6
6
|
import * as m0006 from "./0006-auth";
|
|
7
7
|
import * as m0007 from "./0007-orgs";
|
|
8
|
+
import * as m0008 from "./0008-trigger-metadata";
|
|
8
9
|
/**
|
|
9
10
|
* Schema migrations, applied in array order. Add a new file under
|
|
10
11
|
* `migrations/NNNN-<slug>.ts` exporting `name` and `sql`, then append it
|
|
@@ -20,4 +21,5 @@ export const MIGRATIONS = [
|
|
|
20
21
|
{ name: m0005.name, sql: m0005.sql },
|
|
21
22
|
{ name: m0006.name, sql: m0006.sql },
|
|
22
23
|
{ name: m0007.name, sql: m0007.sql },
|
|
24
|
+
{ name: m0008.name, sql: m0008.sql },
|
|
23
25
|
];
|
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(),
|
package/dist/org-store.d.ts
CHANGED
|
@@ -29,6 +29,10 @@ export interface OrgStore {
|
|
|
29
29
|
}): Promise<OrgRow>;
|
|
30
30
|
getOrg(id: string): Promise<OrgRow | null>;
|
|
31
31
|
listOrgs(): Promise<OrgRow[]>;
|
|
32
|
+
updateOrg(id: string, patch: {
|
|
33
|
+
name?: string;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}): Promise<OrgRow>;
|
|
32
36
|
deleteOrg(id: string): Promise<void>;
|
|
33
37
|
listMembers(orgId: string): Promise<OrgMembershipRow[]>;
|
|
34
38
|
upsertMember(input: {
|
|
@@ -44,12 +48,6 @@ export interface OrgStore {
|
|
|
44
48
|
} | null>;
|
|
45
49
|
/** Orgs the given user is a member of. */
|
|
46
50
|
listOrgsForUser(userId: string): Promise<OrgMembershipRow[]>;
|
|
47
|
-
/**
|
|
48
|
-
* Set the org an already-existing workspace belongs to. Called
|
|
49
|
-
* by the admin endpoint that creates a workspace under an org
|
|
50
|
-
* (key issuance and engram_workspaces.org_id are written together).
|
|
51
|
-
*/
|
|
52
|
-
setWorkspaceOrg(workspaceId: string, orgId: string): Promise<void>;
|
|
53
51
|
/** Workspaces in the given org. */
|
|
54
52
|
listWorkspacesForOrg(orgId: string): Promise<{
|
|
55
53
|
id: string;
|
|
@@ -63,4 +61,7 @@ export interface OrgStore {
|
|
|
63
61
|
}[]>;
|
|
64
62
|
/** Returns true if the user is an org-member of the workspace's org. */
|
|
65
63
|
userCanAccessWorkspace(userId: string, workspaceId: string): Promise<boolean>;
|
|
64
|
+
/** Returns true if the workspace's `org_id` matches. Admin-side check
|
|
65
|
+
* with no user context. */
|
|
66
|
+
workspaceInOrg(orgId: string, workspaceId: string): Promise<boolean>;
|
|
66
67
|
}
|
|
@@ -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 {};
|