본문으로 건너뛰기

Ontology Packaging (L1/L2/L3/L4)

GenD 의 온톨로지는 ADR-006 의 4-Layer Packaging 으로 패키징됩니다. 산업별 납품 + 고객 워크스페이스 확장 + 공통 표준 어휘를 동시에 지원하는 다층 구조입니다.

레이어 정의

Layer패키징 단위변경 권한활성화 시점
L1 표준 어휘GenD 코드 내장 (W3C Time/Org/FOAF)GenD 코어팀무조건 탑재
L2 공통 플랫폼GenD 코드 내장 (DCAT/OpenLineage/Schema.org)GenD 코어팀무조건 탑재
L3 산업 팩ontology_packs 테이블 + Git repoGenD 산업팀Helm value 또는 admin API
L4 워크스페이스DB row (workspace_id 스코프)고객사 admin워크스페이스 활성화 시

데이터 모델 (M1 Step 2 시점)

ontology_packs (신규)

class OntologyPack(Base):
id: UUID # FK 용 안정적 식별자
slug: str # URI 식별자 (e.g. "gend-onto-lng"), unique
name: str # 표시명
version: str # semver (e.g. "0.3.0")
source_url: str # Git repo URL (nullable)
status: str # active | disabled | deprecated
signature: str # signed release signature (M3 검증)
installed_at: datetime

제약:

  • CheckConstraint("slug NOT IN ('core', 'platform')") — L1/L2 reserved slug 충돌 차단
  • CheckConstraint("status IN ('active', 'disabled', 'deprecated')")
  • UniqueConstraint("slug", "version") — 동일 slug 의 다중 버전 immutable snapshot 보존

4 ontology 모델의 FK 승격

모델pack_id FKworkspace_id FKlayer scope CHECK
OntologyClassontology_packs.idworkspaces.idL3→pack, L4→workspace, L1/L2→양쪽 NULL
OntologyProperty동일동일동일
OntologyRelation동일동일동일
OntologyClassGrant동일동일동일

모두 ondelete=RESTRICT — 의존 row 가 있는 pack/workspace 는 실수로 삭제 불가.

URI 풀네이밍 (ADR-006 §URI)

gend://{layer}/{slug}/{ClassName}

# L1 (slug = 고정값 "core")
gend://L1/core/Time

# L2 (slug = 고정값 "platform")
gend://L2/platform/Dataset

# L3 (slug = ontology_packs.slug)
gend://L3/gend-onto-lng/Equipment

# L4 (slug = workspaces.slug)
gend://L4/samsung-card/SamsungCardCustomer

URI 의 {slug} 자리는 사람이 읽는 식별자, DB FK 는 안정적 UUID. slug → id 단방향 lookup.

산업 팩 활성화 절차

Helm value 로 install (선언적)

# infra/helm/gend-api/values.yaml
gend:
ontology:
enabledPacks:
- slug: gend-onto-lng
version: 0.3.0
- slug: gend-onto-finance
version: 0.2.1

부팅 시 자동으로 ontology_packs row 생성 + LinkML YAML 로드 (M1 Step 3 LinkML loader 완성 후).

런타임 admin API (M2)

POST /api/v1/ontology/packs/gend-onto-lng/install
{
"version": "0.3.0",
"source_url": "https://github.com/genonai/gend-onto-lng"
}

L3 / L4 Shadow 규칙 (ADR-006)

동일 name 의 Class 가 L3 와 L4 에 동시 정의되면:

  1. L4 우선 — workspace 내에서 조회 시 L4 정의가 L3 를 shadow
  2. extends 시 L3 version pin 강제 (e.g. extends: gend-onto-finance/Customer@0.3.0)
  3. L3 팩 업그레이드 시 SHACL diff 자동 산출 → breaking change 면 admin 승인 게이트

M1 Step 3 — LinkML loader (#1044)

LinkML YAML 1개를 OntologyPack 스코프로 PG 의 ontology_classes / ontology_properties 에 idempotent 적재.

지원 범위

LinkML 요소PG 매핑비고
classes.<name>ontology_classes row (layer='L3')description/abstract/label
classes.<name>.attributes.<attr>ontology_properties rowrange / cardinality / unit / pii_type
classes.<name>.is_aparent_class_id (Pass 2)동일 YAML 내부만 (cross-pack 은 M2)
slots: + classes.<name>.slots: [...]ontology_properties 평탄화top-level slot
classes.<name>.attributes.<attr>.range 가 classrange_type='class_ref'Relation 모델링은 M2

range 매핑

LinkML 표준 scalar → RANGE_TYPE_VALUES (db.models.ontology):

  • string / str / uri / uriorcuriestring
  • integer / int / longint
  • float / double / decimalfloat
  • datetime / date / timedatetime
  • boolean / boolbool
  • 알려지지 않은 (다른 Class 이름) → class_ref (평탄화)

cardinality 매핑

  • required: true + multivalued: true1..*
  • required: true1..1
  • multivalued: true0..*
  • (없음) → 0..1

Idempotency

동일 (layer, pack_id, workspace_id, name) 4-tuple 의 Class/Property 가 이미 있으면 attribute 갱신 (PUT). 두 번째 load 는 classes_created=0 보고.

사용 예

from gend_api.services.ontology import LinkMLLoader

loader = LinkMLLoader()
report = await loader.load_pack(
yaml_path=Path("gend-onto-lng.yaml"),
pack=lng_pack,
db=session,
)
await session.commit()
print(report.classes_created, report.properties_created)

회귀 가드

  • tests/test_ontology_pack_fk.py (12 cases) — FK target / Unique / Check / scope
  • tests/test_ontology_model.py (6 cases) — layer scope CHECK / shadow isolation
  • tests/test_ontology_linkml_loader.py (25 cases) — Step 3:
    • 기본 load + idempotent + description 갱신
    • range 11종 + cardinality 4종 매핑 (parametrize)
    • is_a 부모 연결 + 알 수 없는 부모 warning
    • top-level slots 평탄화
    • 파일 load + 누락 파일 + top-level 비 mapping 에러
    • 서로 다른 pack 의 동일 Class 이름 격리
  • tests/test_ontology_shacl_validator.py (21 cases) — Step 4:
    • shape 생성 idempotency / required minCount / class_ref nodeKind IRI
    • 빈 instance / required 충족·누락 / multivalued / maxCount 위반 / datatype 위반
    • shape cache hit/invalidate / singleton reuse·reset
    • batch abort_on_first / full run
    • fail-closed (shape build error / pyshacl error)
  • tests/test_ontology_instance_repo.py (23 cases) — Step 5:
    • collection 명명 (Equipment → onto_equipment, snake_case 강제)
    • ensure_collection idempotent (has_collection + create_collection)
    • SHACL fail-closed (validate_instance 호출 + 위반 시 OntologyValidationError raise)
    • get/list/update/delete + singleton reuse/reset
  • tests/test_ontology_lng_seed.py (10 cases) — Step 5 LNG seed:
    • SEED_YAML 파일 존재 + 3 Class + identifier 1개씩
    • upsert_lng_pack idempotent
    • PG-only graceful (ArangoDB 미가용 시 counts 0)
    • ArangoDB mock 8 instances (3+3+2) insert
  • tests/test_arango_collections.pyonto_* prefix 회귀 (PR #1016)

M1 Step 4 — SHACL validator (#1048)

PG 의 ontology_classes / ontology_properties 행을 SHACL NodeShape 으로 동적 변환하고, 인스턴스 dict 가 그 shape 를 준수하는지 pyshacl 로 검증한다. LinkML 패키지에 의존하지 않고 PG 가 진실의 캐시 원천 (Step 3 의 LinkMLLoader 가 YAML → PG 동기화 책임).

Property → SHACL 매핑

OntologyProperty 필드SHACL constraint
range_type='string'sh:datatype xsd:string
range_type='int'sh:datatype xsd:integer
range_type='float'sh:datatype xsd:double (Python float = IEEE 754)
range_type='datetime'sh:datatype xsd:dateTime
range_type='bool'sh:datatype xsd:boolean
range_type='class_ref'sh:nodeKind sh:IRI
cardinality='1..1'sh:minCount 1 + sh:maxCount 1
cardinality='1..*'sh:minCount 1
cardinality='0..1'sh:maxCount 1
cardinality='0..*'(no count constraint)

Usage

from gend_api.services.ontology import get_shacl_validator

validator = get_shacl_validator()
result = await validator.validate_instance(
db=session,
class_row=equipment_class,
instance={
"_key": "T-001",
"tag_id": "T-001",
"design_pressure": 80.5,
},
)
if not result.conforms:
raise HTTPException(422, detail=result.violations)

Fail-closed 정책

  • pyshacl 자체 에러 → conforms=False + 위반 사유 (raise 없음).
  • shape build 단계 에러 → conforms=False (project_context_security 패턴).
  • 빈 instance dict → conforms=False ("instance is empty").

Cache 정책

  • module-level singleton (get_shacl_validator()) — 부팅 시 1회 생성.
  • class_id → shape graph 캐시 — 동일 class 2회 조회 시 cache hit.
  • LinkMLLoader 재실행 / model_registry 갱신 후 호출자가 명시 호출: validator.invalidate_cache(class_id) 또는 invalidate_cache() (전체).

Step 5 와의 경계

본 Step 은 validator service 만 책임. 인스턴스 라우터 (POST /api/v1/ontology/instances/{class}) 의 wire-up 은 Step 5 (ArangoDB instance repo) 에서 — OntologyInstanceRepo.create()validator.validate_instance 를 게이트로 호출.

M1 Step 5 — ArangoDB instance repo + LNG seed (#1052)

ADR-006 4-Layer Packaging 의 인스턴스 영속화 백엔드 (ArangoDB) 도입. OntologyInstanceRepo 가 Class 별 onto_<class> collection CRUD 를 담당하고, create/update 경로에서 Step 4 SHACL validator 가 fail-closed 게이트 — 검증 실패 시 OntologyValidationError raise.

Collection 명명

Class.namecollection
Equipmentonto_equipment
ProcessUnitonto_process_unit
EventLogonto_event_log

CamelCase → snake_case 변환 후 onto_ prefix. test_arango_collections.py 의 #1016 prefix 회귀 가드와 정합.

Usage

from gend_api.services.ontology import (
get_instance_repo, OntologyValidationError,
)

repo = get_instance_repo()
try:
doc = await repo.create(
db=session, class_row=equipment_class,
instance={"_key": "T-001", "tag_id": "T-001", "design_pressure": 80.5},
)
except OntologyValidationError as exc:
# SHACL 검증 실패 — 라우터가 HTTP 422 변환.
raise HTTPException(422, detail=exc.violations)

LNG demo seed (gend-onto-lng)

apps/api/src/gend_api/ontology/seeds/gend-onto-lng.yaml — 3 Class (Equipment / Process / Event), 12 attribute.

스크립트 scripts/seed_ontology_lng.py:

  1. OntologyPack(slug='gend-onto-lng', version='0.1.0') upsert.
  2. LinkMLLoader 로 PG sync.
  3. OntologyInstanceRepo 로 8 instance (Equipment 3 + Process 3 + Event 2) ArangoDB onto_* collection 에 insert (overwrite=True idempotent).

ArangoDB 미가용 환경 (CI smoke / 로컬 PG-only) 에서는 PG 단계만 완료 + ArangoDB 단계는 graceful skip (WARNING 로그) — 부팅이 깨지지 않음.

마일스톤

단계범위상태
M1 Step 1 (#1020)4 모델 + 빈 라우터✅ MERGED
M1 Step 2 (#1023)ontology_packs + FK + slug✅ MERGED
M1 Step 3 (#1044)LinkML loader + PG ↔ YAML sync (load only)
M1 Step 4 (#1048)SHACL validator (pyshacl + rdflib)
M1 Step 5 (#1052)ArangoDB instance repo + LNG demo seedM1 종결
M2 BetaGUI Modeler + L3 산업 팩 확장 + ABAC 통합 + 라우터 wire-up다음
M3 GA마켓플레이스 + 서명 검증 + Hybrid RAG 통합 (#994)

관련