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 repo | GenD 산업팀 | 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 FK | workspace_id FK | layer scope CHECK |
|---|---|---|---|
OntologyClass | → ontology_packs.id | → workspaces.id | L3→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 에 동시 정의되면:
- L4 우선 — workspace 내에서 조회 시 L4 정의가 L3 를 shadow
extends시 L3 version pin 강제 (e.g.extends: gend-onto-finance/Customer@0.3.0)- 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 row | range / cardinality / unit / pii_type |
classes.<name>.is_a | parent_class_id (Pass 2) | 동일 YAML 내부만 (cross-pack 은 M2) |
slots: + classes.<name>.slots: [...] | ontology_properties 평탄화 | top-level slot |
classes.<name>.attributes.<attr>.range 가 class | range_type='class_ref' | Relation 모델링은 M2 |
range 매핑
LinkML 표준 scalar → RANGE_TYPE_VALUES (db.models.ontology):
string/str/uri/uriorcurie→stringinteger/int/long→intfloat/double/decimal→floatdatetime/date/time→datetimeboolean/bool→bool- 알려지지 않은 (다른 Class 이름) →
class_ref(평탄화)
cardinality 매핑
required: true+multivalued: true→1..*required: true→1..1multivalued: true→0..*- (없음) →
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 / scopetests/test_ontology_model.py(6 cases) — layer scope CHECK / shadow isolationtests/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.py—onto_*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.name | collection |
|---|---|
Equipment | onto_equipment |
ProcessUnit | onto_process_unit |
EventLog | onto_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:
OntologyPack(slug='gend-onto-lng', version='0.1.0')upsert.- LinkMLLoader 로 PG sync.
OntologyInstanceRepo로 8 instance (Equipment 3 + Process 3 + Event 2) ArangoDBonto_*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 seed | ✅ M1 종결 |
| M2 Beta | GUI Modeler + L3 산업 팩 확장 + ABAC 통합 + 라우터 wire-up | 다음 |
| M3 GA | 마켓플레이스 + 서명 검증 + Hybrid RAG 통합 (#994) |
관련
- Epic: #993, ADR-006
- 의존 Epic: #1018 Workspace Isolation
- 자매 Epic: #994 Hybrid RAG (Ontology + Vector + Graph)