ONTOLOGICAL DB 문서 원문 보기

프로토콜 진입면 — SQL / Bolt / PostgREST / Studio

이 문서가 답하는 질문

  • 이 시스템에 들어오는 길이 몇 개인가?
  • 각 진입면은 인증·권한·트랜잭션·감사를 어떻게 처리하는가?
  • 어떤 진입면이 무엇을 보장하고, 무엇을 보장하지 않는가?
  • 새 진입면을 추가할 때 지켜야 할 규칙은?

0. 원칙

헌법 원칙 VI: 코어는 하나, 표준은 어댑터로. spec 011 설계 결정 2: 두 번째 인증·감사 경로를 만들지 않는다.

이 두 문장이 모든 진입면의 설계를 규정한다. 진입면은 전송(transport) 이지 의미론(semantics) 이 아니다.


1. 네 개의 진입면 한눈에

① SQL 함수 ② Bolt 4.4 ③ PostgREST ④ Studio HTTP
프로세스 postgres 백엔드 ontological-bolt (별도) PostgREST (외부) Node.js (별도)
필수? ✅ 필수 선택 선택 선택
클라이언트 psql, JDBC, psycopg, supabase-js Neo4j 드라이버 HTTP/REST 브라우저
인증 주체 PostgreSQL 역할 PostgreSQL 역할 (HELLO 자격증명) PostgREST JWT → PG 역할 ❌ 없음 — 고정 역할
권한/RLS PostgreSQL PostgreSQL (동일) PostgreSQL 풀의 고정 역할 권한
트랜잭션 호출자의 것 Bolt BEGIN/COMMIT ↔︎ PG 1:1 요청당 암묵 요청당 암묵
감사 og_audit (session_user) og_audit (인증 역할) og_audit og_audit (고정 역할)
전송 암호화 PostgreSQL SSL 설정 ❌ 평문 (양 구간) 앞단 HTTPS ❌ 평문 HTTP
스트리밍 ❌ (전체 수집 후 반환) ❌ (게이트웨이 메모리에 전체) ❌ ❌

2. ① SQL 함수 표면 — 정본(canonical)

진입점

함수 무엇 휘발성
og_cypher(graph, query, params) Cypher 실행. 행마다 jsonb 객체 하나 volatile
og_cypher_sql(graph, query) 컴파일된 SQL 반환 stable ⚠️
og_cypher_explain(graph, query, analyze) 컬럼 + SQL + 플랜 volatile
og_cypher_check(query) 파싱만. {ok, clauses, write} immutable, parallel_safe
og_cypher_columns(query) RETURN 순서의 컬럼 이름 immutable, parallel_safe
og_cypher_stats() 직전 호출이 바꾼 것 (Neo4j 카운터 철자) volatile, parallel_unsafe
og_typeql(graph, query, params) TypeQL 실행 volatile
og_typeql_sql / og_typeql_check / og_typeql_schema / og_typeql_script 대응 표면 —

전체 78개 #[pg_extern] 목록은 docs/api.md 와 ../00_overview/04_repository_map.md.

이 표면이 보장하는 것

  • 호출자 트랜잭션 안에서 실행된다. 롤백하면 그래프 변경도 롤백된다.
  • PostgreSQL 역할 권한과 RLS가 그대로 적용된다.
  • 결과가 표준 PostgreSQL 타입(jsonb)이라 SQL에서 JOIN 가능하다.

⚠️ 휘발성 표기 문제 (Facts)

og_cypher_sql 은 #[pg_extern(stable)] 인데 (cypher/mod.rs:74), 그 안에서 views::ensure_view() 가 CREATE OR REPLACE VIEW DDL을 실행할 수 있다 (cypher/views.rs:135). stable 은 "이 함수는 데이터베이스를 수정하지 않는다"는 계약이다. → ARCH-03

og_cypher_stats() 가 parallel_unsafe, volatile 인 것은 올바르다 — 직전 호출이 남긴 백엔드 로컬 상태를 읽으며, 같은 연결에서 다음 질의 전에 물어야만 의미가 있다 (cypher/mod.rs:111-119).


3. ② Bolt 4.4 게이트웨이

형태

Neo4j 드라이버 ──bolt://──> ontological-bolt ──postgres(NoTls)──> PostgreSQL
                            (연결당 스레드)      (세션당 연결 1개)

게이트웨이는 상태를 갖지 않는다: 파서 없음, 플래너 없음, 캐시 없음, 사용자 저장소 없음 (bolt/src/main.rs:4-5).

Neo4j 개념 ↔︎ 여기의 대응

Neo4j 여기 근거
database (session(database="x")) graph x session.rs:221-238
HELLO 의 user/password PostgreSQL 역할과 비밀번호. 두 번째 사용자 저장소 없음 session.rs:168-184
Node og_cypher() 의 {_id, _type, …} 를 Bolt Node로 재인코딩 session.rs:487-545
Relationship {_id, _type, _src, _dst, …} 동일
명시적 트랜잭션 세션 연결의 PostgreSQL 트랜잭션 session.rs:208-219
Neo4jError 컴파일러 메시지 그대로, Neo.ClientError.* 코드 아래 session.rs:571-604

neo4j 와 system 은 그래프 이름이 아니다. 드라이버가 애플리케이션이 아무것도 지정하지 않았을 때 보내는 값이므로 "기본값"으로 해석한다 (session.rs:224-229).

명시적 트랜잭션 안에서는 db 가 고정된다. 드라이버가 BEGIN 에 한 번만 이름을 보내고 이후 RUN 에서는 생략하므로, db 부재는 "기본값"이 아니라 "이 트랜잭션이 시작한 그것"이다 (session.rs:231-237).

게이트웨이가 질의를 보는 유일한 지점

EXPLAIN / PROFILE 접두사 처리다. 그리고 그때도 파싱하지 않는다 — og_cypher_check() 에 읽기/쓰기를 물어본다. 그래서 클라이언트가 행동을 결정하는 read/write 판정이 spec 003의 것이며, 두 번째 전송으로 도달할 뿐 재구현되지 않는다 (session.rs:256-266).

EXPLAIN 은 질의를 분류만 하고 실행하지 않는다. 플랜은 없고, 요약의 type 이 r/w 다.

필드 순서

og_cypher() 는 jsonb 객체를 돌려주고 jsonb는 키를 정렬하므로, 행에서 RETURN 순서를 복원할 수 없다. 그래서 og_cypher_columns(query) 에 물어본다 (session.rs:281-289). RETURN * 처럼 파서가 순서를 모르는 경우는 빈 리스트가 오고, 게이트웨이가 첫 행의 키 순서로 폴백한다 — 순서를 지어내지 않는다.

이 진입면의 실측 한계 (Facts)

항목 상태 근거
Bolt 3.x / 5.x 미지원, 협상이 깨끗하게 실패 bolt/README.md
Path 구조체 인코딩하지 않음. 경로 변수는 홉 리스트로 도착 동일
시공간/공간 타입 파라미터에 담기면 거부한다 (조용히 뭉개지 않음) 동일
TLS 종단하지 않음. 앞단 프록시 전제 동일
PG 연결 암호화 cfg.connect(NoTls) session.rs:182
라우팅 neo4j:// 가 붙게만 함. 단일 서버 응답 session.rs:387 주석
스트리밍 ❌ pg.query() 로 전체를 모은 뒤 PULL n 을 그 Vec 에서 서빙 session.rs:291-322
왕복 RUN 하나당 PG 문장 2개 (og_cypher_columns + og_cypher) session.rs:283-299
연결 풀 없음. 세션 1개 = PG 연결 1개 main.rs:69-79
bookmark 항상 "ontological:0" 상수 session.rs:218

→ ARCH-07

검증 방식

tests/neo4j-movies/run.py 가 Neo4j 공식 드라이버로 Neo4j 공식 Movie 샘플을 돌려 PostgreSQL 경로 / Bolt 경로 / 실제 Neo4j 세 곳의 답을 대조한다. "우리가 만든 클라이언트로 우리 서버를 테스트하는 것은 증거가 아니다" (specs/011-bolt-protocol-gateway/plan.md).

Neo4j 공식 MCP 서버 mcp-neo4j-cypher 가 무수정으로 동작한다 (examples/meeting-rooms/).


4. ③ PostgREST / Supabase RPC

#[pg_extern]
fn og_cypher_json(graph: &str, query: &str, params: default!(JsonB, "'{}'")) -> JsonB {
    // SELECT coalesce(jsonb_agg(r), '[]'::jsonb) FROM og_cypher($1,$2,$3) r
}

— engine/src/interop/mod.rs:34-54

한 번 호출, JSON 배열 하나. PostgREST가 이 함수를 RPC로 노출하면 supabase-js가 그대로 부를 수 있다.

RLS가 순회 중간에 적용된다

이 진입면의 핵심 보장이다:

//! The load-bearing observation: a compiled Cypher query reads ordinary tables.
//! So row-level security applied to those tables applies **mid-traversal**, with
//! no enforcement code of our own — a node the caller cannot see simply fails to
//! join, and every path through it disappears (FR-013).

— interop/mod.rs:3-7

og_enable_rls(graph, type_name, policy_expr) 가 타입과 모든 서브타입의 스토리지 테이블에 정책을 건다 (interop/mod.rs:18-32).

⚠️ policy_expr 은 SQL 불리언 표현식으로 그대로 보간된다 (CREATE POLICY og_policy ON {table} USING ({policy_expr})). 이것은 DDL 인자이고 호출자가 이미 DDL 권한을 가진 상황이지만, 사용자 입력을 여기 넘기면 안 된다. 07_security/ 담당이 다룰 항목이다.

관계형 노출

방향 수단
그래프 → SQL og_node_view, og_edge_view, og_type_view, og_property_view, og_role_view (access.sql:81-126)
SQL → 그래프 og_map_table() — 기존 테이블을 복사 없이 노드 타입으로 노출
TypeQL 매핑 노출 og_typeql_attribute, og_typeql_role (access.sql:297-338)

5. ④ Studio HTTP

브라우저 ──HTTP:7474──> portal/server/index.js ──pg Pool(max=8)──> PostgreSQL

엔드포인트

메서드 · 경로 무엇
GET /api/benchmark bench/results/ JSON
GET /api/health 상태
GET /api/schema og_schema() 프록시
GET /api/audit og_audit 조회
POST /api/cypher og_cypher() 실행 + 그래프 투영
POST /api/explain og_cypher_explain()
POST /api/diagnose og_diagnose_empty()
POST /api/expand 노드 확장
POST /api/sql 임의 SQL 실행

— portal/server/index.js:140-309

⚠️ 이 진입면의 보안 모델 (Facts)

const pool = new Pool({
  host: process.env.PGHOST || 'localhost',
  user: process.env.PGUSER || 'dev',
  password: process.env.PGPASSWORD || undefined,
  max: 8,
});

— portal/server/index.js:21-29

  • HTTP 인증 계층이 없다. 요청자 신원 확인 코드가 없다.
  • 모든 요청이 하나의 고정 역할로 실행된다. 따라서 RLS가 요청자별로 적용되지 않고, og_audit.principal 도 그 고정 역할로 기록된다.
  • POST /api/sql 이 임의 SQL을 그 역할로 실행한다 (pool.query(sql), index.js:296-308).
  • 평문 HTTP (http.createServer).

이것은 로컬 개발 콘솔의 위협 모델이다. README.md 의 실행 예시도 PGHOST=127.0.0.1 로컬을 전제한다. → 네트워크에 노출하면 안 된다. 07_security/ 와 08_operations/ 담당이 배포 가드를 정의해야 한다.

다만 이 서버의 설계 의도는 명시되어 있다: "Deliberately thin: … All the intelligence lives in the database, which is the point — anything this server can do, a psql session can do too." (index.js:2-9) 즉 추가 권한을 만들지 않았다는 것이 방어 논리이고, 그 논리는 고정 역할이 최소 권한일 때만 성립한다.


6. 진입면 공통 불변식

모든 진입면이 지켜야 하는 것:

불변식 왜
Cypher/TypeQL은 한 곳에서만 해석된다 (spec 003 / 010) 두 번째 구현은 두 번째 의미론이 된다
인증은 PostgreSQL 역할 하나뿐이다 두 번째 사용자 저장소는 권한·RLS·감사를 분리시킨다
트랜잭션은 PostgreSQL 트랜잭션에 1:1 대응한다 헌법 원칙 IX
오류 메시지는 엔진의 것을 그대로 전달한다 헌법 원칙 VIII (교정 가능한 오류)
감사는 og_audit 하나에 남는다 감사 경로가 갈라지면 감사가 아니다

현재 위반 (Facts)

진입면 위반 심각도
Studio 인증이 PostgreSQL 역할이 아니라 "없음". 감사가 고정 역할로 기록됨 높음 (네트워크 노출 시)
Bolt 인증은 지켜짐. 전송 암호화가 두 구간 모두 없음 중 (배포 가이드로 완화)

Decisions

# 결정 근거
D-1 Bolt는 별도 프로세스 SPI가 스레드 안전하지 않아 배경 워커는 세션을 직렬화 (spec 011 plan 결정 1)
D-2 Bolt는 자체 인증 저장소를 만들지 않는다 권한·RLS·감사가 하나로 유지 (spec 011 plan 결정 2)
D-3 게이트웨이는 필드 이름을 파서에서 가져온다 jsonb가 키를 정렬해 행에서 복원 불가 (결정 3)
D-4 연결 하나에 스레드 하나 동시성 한계를 PostgreSQL 연결 수 한계와 같게 (결정 4)
D-5 Bolt 5.x를 조용히 절반만 구현하지 않는다 지원 매트릭스에 "미지원"으로 명시 (FR-020)
D-6 TLS는 앞단에 맡긴다 문서화된 한계 (spec 011 Complexity Tracking)
D-7 Studio는 의도적으로 얇다 이 서버가 할 수 있는 것은 psql 세션도 할 수 있다

Facts

  • 네 진입면 중 필수는 SQL 함수 하나다. 나머지를 다 꺼도 데이터베이스는 완전하다.
  • ontological-bolt 를 멈춰도 og_cypher() 호출자는 영향받지 않는다.
  • Bolt 세션 하나가 PostgreSQL 연결 하나를 점유한다 → Bolt 동시 세션 상한 = max_connections.
  • Studio 풀은 max: 8 이다.

Forbidden / Required

Forbidden

  • ❌ 어떤 진입면에도 Cypher/TypeQL 파서·플래너·캐시를 추가하는 것.
  • ❌ 두 번째 사용자/비밀번호 저장소를 만드는 것.
  • ❌ 진입면에서 오류 메시지를 재작성하는 것 — 엔진의 교정 제안이 소실된다.
  • ❌ 진입면별로 별도 감사 로그를 만드는 것.
  • ❌ Studio를 네트워크에 노출하는 것 (현재 인증 계층 없음).
  • ❌ og_enable_rls 의 policy_expr 에 사용자 입력을 전달하는 것.

Required

  • ✅ 새 진입면을 추가하면 위 "진입면 공통 불변식" 표의 5개 항목을 모두 만족시킬 것.
  • ✅ 지원/미지원을 매트릭스로 문서화할 것. "지원함"이라는 모호한 표현 금지 (헌법 기술 제약).
  • ✅ 진입면의 지원 범위가 코어보다 좁아질 수는 있어도 넓어져서는 안 된다.
  • ✅ 새 SQL 노출 API에는 RLS/권한 테스트를 함께 추가할 것 (헌법 품질 게이트).
이 페이지는 docs/01_architecture/06_protocol_surfaces.md 에서 만들어졌습니다 · python3 web/build_docs.py