@jungjaehoon/mama-core 2.1.2 → 2.2.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.
@@ -0,0 +1,27 @@
1
+ CREATE TABLE IF NOT EXISTS principals (
2
+ principal_id TEXT PRIMARY KEY,
3
+ kind TEXT NOT NULL CHECK (kind IN ('owner', 'member')),
4
+ display_name TEXT,
5
+ status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'offboarded')),
6
+ created_at INTEGER NOT NULL,
7
+ updated_at INTEGER NOT NULL
8
+ );
9
+
10
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_principals_at_most_one_owner
11
+ ON principals(kind)
12
+ WHERE kind = 'owner' AND status = 'active';
13
+
14
+ CREATE TABLE IF NOT EXISTS external_identities (
15
+ connector TEXT NOT NULL,
16
+ namespace TEXT NOT NULL,
17
+ external_id TEXT NOT NULL,
18
+ principal_id TEXT NOT NULL REFERENCES principals(principal_id),
19
+ created_at INTEGER NOT NULL,
20
+ PRIMARY KEY (connector, namespace, external_id)
21
+ );
22
+
23
+ CREATE INDEX IF NOT EXISTS idx_external_identities_principal
24
+ ON external_identities(principal_id);
25
+
26
+ INSERT OR IGNORE INTO schema_version (version, description)
27
+ VALUES (64, 'Create principals and external_identities for member registry');
@@ -32,9 +32,9 @@ exports.SHUTDOWN_TOKEN = process.env.MAMA_SHUTDOWN_TOKEN || crypto_1.default.ran
32
32
  const embeddings_js_1 = require("../embeddings.js");
33
33
  const config_loader_js_1 = require("../config-loader.js");
34
34
  const db_manager_js_1 = require("../db-manager.js");
35
- // Import Session API handler (Mobile Chat)
35
+ // Import Session API handler (chat session API)
36
36
  const session_api_js_1 = require("./mobile/session-api.js");
37
- // Import WebSocket handler (Mobile Chat - V2 with MessageRouter)
37
+ // Import WebSocket handler (chat WebSocket API - V2 with MessageRouter)
38
38
  const websocket_handler_js_1 = require("./mobile/websocket-handler.js");
39
39
  // Import Session Manager
40
40
  const session_manager_js_1 = require("./mobile/session-manager.js");
@@ -95,7 +95,7 @@ async function handleRequest(req, res) {
95
95
  // Security Note: CORS '*' is safe here because:
96
96
  // 1. Server binds to localhost only (127.0.0.1)
97
97
  // 2. No sensitive data exposed (user's own decisions)
98
- // 3. Required for browser-based Graph Viewer
98
+ // 3. Required for the browser-based Viewer
99
99
  res.setHeader('Content-Type', 'application/json');
100
100
  res.setHeader('Access-Control-Allow-Origin', '*');
101
101
  // Health check
@@ -212,7 +212,7 @@ async function handleRequest(req, res) {
212
212
  }
213
213
  return;
214
214
  }
215
- // Session API routes (Mobile Chat)
215
+ // Session API routes (chat session API)
216
216
  try {
217
217
  const sessionHandled = await sessionHandler(req, res);
218
218
  if (sessionHandled) {
@@ -257,7 +257,7 @@ async function handleRequest(req, res) {
257
257
  <body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e; color: #eee;">
258
258
  <div style="text-align: center; padding: 2rem;">
259
259
  <h1>🧠 MAMA Viewer</h1>
260
- <p>Viewer and Chat features are only available in <strong>Standalone</strong> mode.</p>
260
+ <p>The Viewer is only available in <strong>Standalone</strong> mode.</p>
261
261
  <p style="color: #888; margin-top: 1rem;">Start Standalone: <code style="background: #333; padding: 0.25rem 0.5rem; border-radius: 4px;">mama start</code></p>
262
262
  <p style="color: #666; font-size: 0.9rem; margin-top: 2rem;">Current: MCP Server (embedding only)</p>
263
263
  </div>
@@ -313,7 +313,7 @@ async function startEmbeddingServer(port = exports.DEFAULT_PORT, options = {}) {
313
313
  // Check if HTTP server is disabled
314
314
  if (process.env.MAMA_DISABLE_HTTP_SERVER === 'true') {
315
315
  console.error('[EmbeddingHTTP] HTTP server disabled via MAMA_DISABLE_HTTP_SERVER');
316
- console.error('[EmbeddingHTTP] Graph Viewer and Mobile Chat will not be available');
316
+ console.error('[EmbeddingHTTP] The Viewer and the chat WebSocket API will not be available');
317
317
  return null;
318
318
  }
319
319
  // Initialize database for graph API
@@ -0,0 +1,37 @@
1
+ import type { DatabaseAdapter as DBManagerAdapter } from '../db-manager.js';
2
+ export interface PrincipalRow {
3
+ principalId: string;
4
+ kind: 'owner' | 'member';
5
+ status: 'active' | 'suspended' | 'offboarded';
6
+ }
7
+ export interface PrincipalRepository {
8
+ resolveByExternal(connector: string, namespace: string, externalId: string): PrincipalRow | null;
9
+ registerMember(input: {
10
+ displayName?: string;
11
+ connector: string;
12
+ namespace: string;
13
+ externalId: string;
14
+ now: number;
15
+ }): string;
16
+ bindIdentity(principalId: string, connector: string, namespace: string, externalId: string, now: number): void;
17
+ suspend(principalId: string, now: number): void;
18
+ offboard(principalId: string, now: number): void;
19
+ ensureOwner(input: {
20
+ connector: string;
21
+ namespace: string;
22
+ externalId: string;
23
+ now: number;
24
+ }): 'created' | 'exists' | 'conflict';
25
+ listMembers(): Array<{
26
+ principalId: string;
27
+ displayName?: string;
28
+ status: string;
29
+ }>;
30
+ }
31
+ export type PrincipalRegistrationErrorCode = 'identity_bound_to_owner' | 'member_not_active';
32
+ export declare class PrincipalRegistrationError extends Error {
33
+ readonly code: PrincipalRegistrationErrorCode;
34
+ constructor(code: PrincipalRegistrationErrorCode, message: string);
35
+ }
36
+ export declare function createPrincipalRepository(adapter: Pick<DBManagerAdapter, 'prepare' | 'transaction'>): PrincipalRepository;
37
+ //# sourceMappingURL=principal-repository.d.ts.map
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrincipalRegistrationError = void 0;
4
+ exports.createPrincipalRepository = createPrincipalRepository;
5
+ const node_crypto_1 = require("node:crypto");
6
+ class PrincipalRegistrationError extends Error {
7
+ code;
8
+ constructor(code, message) {
9
+ super(message);
10
+ this.name = 'PrincipalRegistrationError';
11
+ this.code = code;
12
+ }
13
+ }
14
+ exports.PrincipalRegistrationError = PrincipalRegistrationError;
15
+ function mintPrincipalId(kind, connector, namespace, externalId) {
16
+ const identityKey = JSON.stringify([kind, connector, namespace, externalId]);
17
+ const digest = (0, node_crypto_1.createHash)('sha256').update(identityKey, 'utf8').digest('hex');
18
+ return `principal_${digest}`;
19
+ }
20
+ function mapPrincipal(row) {
21
+ return {
22
+ principalId: row.principal_id,
23
+ kind: row.kind,
24
+ status: row.status,
25
+ };
26
+ }
27
+ function createPrincipalRepository(adapter) {
28
+ const resolveByExternalStatement = adapter.prepare(`SELECT p.principal_id, p.kind, p.status
29
+ FROM external_identities AS e
30
+ JOIN principals AS p ON p.principal_id = e.principal_id
31
+ WHERE e.connector = ? AND e.namespace = ? AND e.external_id = ?`);
32
+ const insertPrincipalStatement = adapter.prepare(`INSERT INTO principals (
33
+ principal_id, kind, display_name, status, created_at, updated_at
34
+ ) VALUES (?, ?, ?, 'active', ?, ?)`);
35
+ const insertIdentityStatement = adapter.prepare(`INSERT INTO external_identities (
36
+ connector, namespace, external_id, principal_id, created_at
37
+ ) VALUES (?, ?, ?, ?, ?)`);
38
+ const selectPrincipalKindStatement = adapter.prepare('SELECT kind FROM principals WHERE principal_id = ?');
39
+ const updatePrincipalStatusStatement = adapter.prepare('UPDATE principals SET status = ?, updated_at = ? WHERE principal_id = ?');
40
+ const selectActiveOwnerStatement = adapter.prepare("SELECT principal_id FROM principals WHERE kind = 'owner' AND status = 'active' LIMIT 1");
41
+ const listMembersStatement = adapter.prepare(`SELECT principal_id, display_name, status
42
+ FROM principals
43
+ WHERE kind = 'member'
44
+ ORDER BY created_at ASC, principal_id ASC`);
45
+ function resolveByExternal(connector, namespace, externalId) {
46
+ const row = resolveByExternalStatement.get(connector, namespace, externalId);
47
+ return row ? mapPrincipal(row) : null;
48
+ }
49
+ function bindIdentity(principalId, connector, namespace, externalId, now) {
50
+ insertIdentityStatement.run(connector, namespace, externalId, principalId, now);
51
+ }
52
+ function registerMember(input) {
53
+ const existing = resolveByExternal(input.connector, input.namespace, input.externalId);
54
+ if (existing) {
55
+ if (existing.kind === 'member' && existing.status === 'active') {
56
+ return existing.principalId;
57
+ }
58
+ if (existing.kind === 'owner') {
59
+ throw new PrincipalRegistrationError('identity_bound_to_owner', 'External identity is already bound to an owner principal');
60
+ }
61
+ throw new PrincipalRegistrationError('member_not_active', 'External identity is already bound to a non-active member principal');
62
+ }
63
+ const principalId = mintPrincipalId('member', input.connector, input.namespace, input.externalId);
64
+ adapter.transaction(() => {
65
+ insertPrincipalStatement.run(principalId, 'member', input.displayName ?? null, input.now, input.now);
66
+ bindIdentity(principalId, input.connector, input.namespace, input.externalId, input.now);
67
+ });
68
+ return principalId;
69
+ }
70
+ function transitionMember(principalId, status, now) {
71
+ const principal = selectPrincipalKindStatement.get(principalId);
72
+ if (!principal) {
73
+ throw new Error(`Principal not found: ${principalId}`);
74
+ }
75
+ if (principal.kind === 'owner') {
76
+ throw new Error(`Owner principals cannot be ${status}`);
77
+ }
78
+ updatePrincipalStatusStatement.run(status, now, principalId);
79
+ }
80
+ function ensureOwner(input) {
81
+ return adapter.transaction(() => {
82
+ const existing = resolveByExternal(input.connector, input.namespace, input.externalId);
83
+ if (existing) {
84
+ return existing.kind === 'owner' ? 'exists' : 'conflict';
85
+ }
86
+ if (selectActiveOwnerStatement.get()) {
87
+ return 'conflict';
88
+ }
89
+ const principalId = mintPrincipalId('owner', input.connector, input.namespace, input.externalId);
90
+ insertPrincipalStatement.run(principalId, 'owner', null, input.now, input.now);
91
+ bindIdentity(principalId, input.connector, input.namespace, input.externalId, input.now);
92
+ return 'created';
93
+ });
94
+ }
95
+ function listMembers() {
96
+ return listMembersStatement.all().map((row) => {
97
+ const member = {
98
+ principalId: row.principal_id,
99
+ status: row.status,
100
+ };
101
+ return row.display_name === null ? member : { ...member, displayName: row.display_name };
102
+ });
103
+ }
104
+ return {
105
+ resolveByExternal,
106
+ registerMember,
107
+ bindIdentity,
108
+ suspend(principalId, now) {
109
+ transitionMember(principalId, 'suspended', now);
110
+ },
111
+ offboard(principalId, now) {
112
+ transitionMember(principalId, 'offboarded', now);
113
+ },
114
+ ensureOwner,
115
+ listMembers,
116
+ };
117
+ }
118
+ //# sourceMappingURL=principal-repository.js.map
package/dist/index.d.ts CHANGED
@@ -72,6 +72,7 @@ export * from './cases/wiki-page-index.js';
72
72
  export * from './connectors/event-index.js';
73
73
  export * from './connectors/raw-query.js';
74
74
  export * from './connectors/types.js';
75
+ export * from './identity/principal-repository.js';
75
76
  export * from './search/question-type.js';
76
77
  export * from './search/feedback-store.js';
77
78
  export * from './search/ranker-features.js';
package/dist/index.js CHANGED
@@ -264,6 +264,7 @@ __exportStar(require("./cases/wiki-page-index.js"), exports);
264
264
  __exportStar(require("./connectors/event-index.js"), exports);
265
265
  __exportStar(require("./connectors/raw-query.js"), exports);
266
266
  __exportStar(require("./connectors/types.js"), exports);
267
+ __exportStar(require("./identity/principal-repository.js"), exports);
267
268
  __exportStar(require("./search/question-type.js"), exports);
268
269
  __exportStar(require("./search/feedback-store.js"), exports);
269
270
  __exportStar(require("./search/ranker-features.js"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jungjaehoon/mama-core",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "MAMA Core - Shared modules for Memory-Augmented MCP Assistant",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",