Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
`ontology_concepts`, `concept_edges`(→ `graph_edges`), `dataset_nodes`, `graph_nodes`,
`embedding_vectors`, `config_entries`, `schema_migrations`.

DiskSage의 검증된 복사 이후 lineage를 위한 정규화 ERD와 pg-erd 재현 절차는
[`docs/disksage-copy-lineage-erd.md`](docs/disksage-copy-lineage-erd.md)에 있습니다.

## 목표

- 데이터 탐색: 키워드 카탈로그 검색 + 유사어/용어(ontology) 해석 + **그래프 순회** + **시맨틱 검색**
Expand Down Expand Up @@ -105,6 +108,78 @@ SDP_DATABASE_DSN='postgresql+psycopg://sdp_graph_app:<url-encoded-password>@loca
- `GET /ontology/term/{term}/graph` — 개념 그래프 (그래프 스토어 백엔드)
- `POST /search/semantic` — pgvector KNN 시맨틱 검색 (kind 필터)

### 파일 지식 온톨로지

`CWL File Knowledge Profile 0.1`은 파일 내용의 SHA-256 정체성과 물리 저장 위치를
분리합니다. 같은 bytes가 로컬/Synology 동기화 폴더, S3, S3 호환 저장소, Azure Blob에
복제되어도 하나의 `FileAsset`과 여러 DCAT `Distribution`으로 표현됩니다. 기계 판독
프로파일과 shape는 `ontology/cwl-file-profile.ttl`, `ontology/cwl-file-shapes.ttl`에 있으며,
ingest/validate 때 pySHACL로 실제 실행됩니다.

- `POST /file-assets` — 관리자 정책을 통과한 자산·후보 주장 적재
- `POST /file-assets/preview/disksage` — DiskSage 복사 전 후보의 비영속·경로 비노출 온톨로지 미리보기
- `GET /file-assets/{asset_id}` — 자산과 의미 관계 조회
- `GET /file-assets/{asset_id}/jsonld` — 기본 locator 비공개 JSON-LD
- `GET /file-assets/{asset_id}/validate` — pySHACL 검증 리포트
- 지원 reader: `filesystem`(로컬/UNC/Synology 포함), `s3`, `s3_compatible`, `azure_blob`
- 지원 본문 추출: TXT/Markdown/CSV/JSON/XML, DOCX/PPTX/XLSX, PDF

파일 의미 추출과 embedding은 OpenAI를 직접 호출하지 않고
[`ContextualWisdomLab/contextual-orchestrator`](https://github.com/ContextualWisdomLab/contextual-orchestrator)만
사용합니다. 의미 추출은 `/v1/chat/completions`, embedding은 orchestrator에 추가된 동기
`/v1/embeddings`를 사용합니다. `orchestrator_base_url`, `semantic_model`,
`embedding_model`은 `config_entries` KV 설정이고, inference token은 주입된 credential
registry에서만 가져옵니다. `embedding_dimension`은 `/v1/embeddings`의 `dimensions`로
전달되어 pgvector 차원과 일치해야 합니다. 운영 graph store도 같은 orchestrator client의
`embed_one`을 ingest와 검색에 주입합니다. OpenAI/provider key는 포털에 두지 않습니다.

`/file-assets/*`는 요청 본문이나 query의 `actor`를 신뢰하지 않습니다. 검증된 OIDC Bearer
토큰에서 subject/role/tenant를 도출합니다. `FileAsset.tenant_id`와 중앙 policy decision을
대조해 tenant 경계를 적용하며, locator 포함 응답은 같은 tenant의 `admin` 또는
`platform-admin`만 요청할 수 있습니다. 같은 SHA-256이나 파일 관계 대상이 다른 tenant에
이미 속하면 ingest를 거부해 distribution/assertion을 섞지 않습니다. `/graph/nodes`,
`/graph/edges`, `/graph/query`, `/search/semantic`, `/ontology/concepts`도 동일한 Bearer
context를 요구하고 body `actor`를 거부합니다. 일반 graph API는 governed file
node/edge를 수정할 수 없으며, traversal/search 결과는 tenant로 필터링되고 locator는
항상 redaction됩니다.

`POST /file-assets/preview/disksage`는 `disksage.file-catalog-candidate-batch` v1만
받습니다. 본문은 2 MiB, 후보는 200건으로 제한되며 `src`, `dst`, `filename`,
`relative_path`, account/object id 같은 저장 위치 식별자는 계약에 존재하지 않고
알 수 없는 필드는 거부됩니다. 생산일은
`embedded_metadata → explicit_filename_date → filesystem_created → filesystem_modified`
순서가 고정되어 있고, 선택된 값은 같은 날짜·source의 metadata evidence에 결합되어야
합니다. 파일명 날짜는 embedded metadata가 없을 때만 낮은 신뢰도의 보조값으로
허용됩니다.

이 endpoint는 deterministic archive-kind→artifact-type 제안만 만들며 LLM, graph
store, file-asset ingest를 호출하지 않습니다. 응답은 title/author/context/evidence
값을 되돌려주지 않고 `content_sha256`와 verified distribution이 없으므로
`persistable_as_file_asset=false`를 명시합니다. 또한 copy/eviction 허가가 아니며,
기존 create-file 정책을 통과한 관리자만 사용할 수 있습니다. 중앙 policy decision
증빙은 기록되지만 catalog/file asset 자체는 저장되지 않습니다.

GitHub Secret에 값을 저장하는 것만으로는 런타임 주입이 되지 않습니다. 배포 호스트는
secret manager에서 token을 읽는 `CredentialRegistry` 구현을 만든 뒤
`sdp.api.create_app(registry)`로 ASGI 앱을 구성해야 합니다. KV에
`orchestrator_base_url`이 있는데 `CONTEXTUAL_ORCHESTRATOR_TOKEN`이 주입되지 않으면
lifespan/startup이 fail-closed하며, registry 교체 시 credential을 캡처한 graph store도
폐기·재생성됩니다. TTL profile/shape는 `sdp/resources/*.ttl` package data로 배포되므로
wheel과 공식 컨테이너에서도 pySHACL 검증이 동일하게 동작합니다.

읽기 전용 로컬 파일럿은 다음처럼 실행합니다. `--no-llm`은 파일 이동·삭제나 네트워크
호출 없이 중복·추출 상태만 확인합니다.

```powershell
$env:PYTHONPATH='src'
py -m sdp.file_pilot --root '<approved-read-only-root>' --output '<local-gitignored-manifest.json>' --name-regex '효성중공업|중공업VOC' --max-files 12 --no-llm
```

LLM을 사용할 때는 `--orchestrator-url`을 주거나 KV의 `orchestrator_base_url`을 사용하며,
inference token은 숨김 prompt로만 입력합니다. manifest에는 원문 조각·근거 인용문·API
응답·credential을 저장하지 않습니다. 실제 파일명과 locator가 들어가므로 출력은 로컬의
Git 제외 경로에만 보관합니다.

### Catalog / governance / enterprise (기존)

- `GET /health`
Expand Down Expand Up @@ -154,6 +229,7 @@ PYTHONPATH=src python -m sdp.demo_smoke
| Browse/Query | `src/sdp/browse.py`, `/browse/*` |
| Policy Service | `src/sdp/policy.py`, `/policy/decision` |
| LLM Orchestrator | `src/sdp/orchestrator.py`, `/llm/*` |
| File Knowledge Profile | `src/sdp/file_ontology.py`, `src/sdp/storage_readers.py`, `src/sdp/document_semantics.py`, `src/sdp/file_pilot.py`, `/file-assets/*` |
| JSON-LD Export | `/catalog/datasets/{id}/jsonld` |
| Enterprise Core Contracts | `src/sdp_core/contracts.py`, `src/sdp_core/readiness.py`, `src/sdp_core/demo_seed.py`, `src/sdp_core/enterprise.py`, `src/sdp_core/rbac.py`, `src/sdp/enterprise_evidence.py`, `src/sdp/semantic_validation.py`, `src/sdp/steward_review.py`, `src/sdp/observability.py`, `/enterprise/*` |

Expand Down
65 changes: 65 additions & 0 deletions docs/disksage-copy-lineage-erd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# DiskSage post-copy lineage ERD

This relational read-model records only integrity-verified copy lineage after
the corresponding file asset and distribution graph nodes exist. It does not
authorize a copy, provider write, sync, or local eviction. Source paths are not
stored; the ingress contract supplies only `source_locator_sha256`.

```mermaid
erDiagram
graph_nodes ||--o| file_asset_records : "content-addressed projection"
graph_nodes ||--o| file_distribution_records : "distribution projection"
file_asset_records ||--o{ file_distribution_records : "has location"
file_asset_records ||--o{ cloud_copy_receipts : "has verified copy"
file_distribution_records ||--o{ cloud_copy_receipts : "is destination of"
cloud_copy_receipts ||--o{ file_metadata_evidence_records : "selects production-time evidence"
cloud_copy_receipts ||--o{ cloud_sync_evidence_records : "has provider observations"
```

The schema deliberately leaves the generic `graph_edges` mirror unchanged.
Its seed path can create relationships before both endpoint nodes exist, while
post-copy lineage requires pre-existing asset and distribution nodes. All seven
new foreign keys use `ON DELETE RESTRICT` so catalog or graph cleanup cannot
silently erase receipt evidence.

## Reproducible pg-erd snapshot

After applying `migrations/0001_init_graph_vector.sql` and
`migrations/0002_file_copy_lineage.sql` to a temporary PostgreSQL 17.10 database,
capture the catalog through the Unix-socket-only pg-erd CLI:

```bash
pg-erd-snapshot \
--host /tmp \
--database semantic_data_portal_erd \
--schema public \
--pretty > semantic-data-portal.snapshot.json
```

The validated snapshot contained 12 relations, 100 columns, 44 constraints,
24 indexes, 16 primary-key columns, and 7 foreign-key edges. The pre-migration
snapshot contained 7 relations and no foreign-key edges.

The seven observed foreign-key edges were:

| Child | Column | Parent | Column |
| --- | --- | --- | --- |
| `file_asset_records` | `graph_node_id` | `graph_nodes` | `node_id` |
| `file_distribution_records` | `graph_node_id` | `graph_nodes` | `node_id` |
| `file_distribution_records` | `asset_node_id` | `file_asset_records` | `graph_node_id` |
| `cloud_copy_receipts` | `asset_node_id` | `file_asset_records` | `graph_node_id` |
| `cloud_copy_receipts` | `destination_distribution_node_id` | `file_distribution_records` | `graph_node_id` |
| `file_metadata_evidence_records` | `receipt_id` | `cloud_copy_receipts` | `receipt_id` |
| `cloud_sync_evidence_records` | `receipt_id` | `cloud_copy_receipts` | `receipt_id` |

## Persistence boundary

- `local_copy_verified` must be true for every persisted receipt.
- `provider_sync_confirmed` remains distinct from local copy verification and
may only be updated from a bound provider evidence record.
- Human-review fields are all required for review-gated copies and must record
an approved disposition; they must all be absent for non-review candidates.
- Production time remains bound to ordered metadata evidence. The portal does
not reinterpret filename dates as embedded production metadata.
- Provider authority, quota, and tenant policy are upstream copy gates. A row in
this read-model is evidence, not permission to copy or evict.
30 changes: 28 additions & 2 deletions docs/implementation-compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,39 @@
- `tests/test_api.py::test_enterprise_console_renders_operator_surface`
- `tests/test_api.py::test_enterprise_demo_smoke_summary_is_ready`

## 7) 다음 단계 (현재 브랜치에서 미반영 권고)
## 7) File Knowledge / Hybrid Ontology

- 표준 프로파일: RDF/OWL 2/SKOS/SHACL/DCAT 3/DCMI/PROV-O/SPDX/JSON-LD 기반 `ontology/cwl-file-profile.ttl`, `ontology/cwl-file-shapes.ttl`; `validate_file_asset`가 pySHACL로 shape를 실행한다.
- 정체성/위치 분리: SHA-256 `FileAsset` 하나에 filesystem/S3/S3-compatible/Azure Blob `Distribution` 여러 개를 결합한다.
- 읽기 전용 수집: `src/sdp/storage_readers.py`는 list/read만 제공하고 이동·삭제·원격 mutation 기능이 없다.
- 의미 근거: `SemanticAssertion`은 confidence, proposed review status, chunk SHA-256, 문자 offset을 보존하며 원문 인용은 저장하지 않는다.
- LLM 경계: `src/sdp/document_semantics.py::ContextualOrchestratorClient`가 `/v1/chat/completions`와 `/v1/embeddings`만 호출한다. 포털에는 OpenAI/provider key가 없다.
- provider 중립성: Synology는 filesystem 배치일 뿐 필수 구성요소가 아니며, 파일 정체성은 저장소 URL과 독립적이다.
- 정책/API: `POST /file-assets`, `GET /file-assets/{asset_id}`, `/jsonld`, `/validate`는 검증된 OIDC Bearer actor context와 저장된 `FileAsset.tenant_id`를 중앙 policy decision에 전달한다. body/query `actor`는 권한 근거가 아니며 locator 공개는 동일 tenant의 admin 또는 platform-admin이 필요하다.
- DiskSage pre-copy adapter: `POST /file-assets/preview/disksage`는 2 MiB/200건으로 제한된 strict v1 batch만 받아 원본 path/file name/account/object id 없이 deterministic `hasArtifactType` 제안을 반환한다. 선택 생산일은 `embedded metadata → explicit filename date → filesystem creation → modification`과 matching evidence를 강제한다. content metadata는 응답에 echo하지 않고 graph/file asset을 저장하거나 LLM을 호출하지 않으며 copy/eviction 허가도 만들지 않는다. create-file policy decision 증빙만 기록한다.
- 그래프 격리: 같은 SHA 및 파일 관계 대상의 cross-tenant merge를 거부한다. generic graph mutation은 file/distribution node를 다룰 수 없고 graph traversal/semantic search는 OIDC tenant policy로 file node를 필터링하며 locator를 redaction한다.
- 운영 패키징: SHACL/OWL TTL은 `sdp/resources/*.ttl` package data로 wheel/container에 포함되고, `orchestrator_base_url` 구성 시 app lifespan은 runtime `CredentialRegistry` token이 없으면 fail-closed한다.
- 벡터 일관성: KV `embedding_dimension`을 orchestrator `dimensions`에 전달하고 같은 orchestrator embedder를 memory/Postgres graph store의 ingest와 검색에 주입한다.
- 파일럿: `src/sdp/file_pilot.py`가 content deduplication, 안전한 문서 추출, graph projection, 로컬 전용 manifest를 수행한다.
- 2026-07-21 live 파일럿: `contextual-orchestrator`를 거친 OpenAI structured output으로 12개 파일을 10개 content-addressed 자산과 12개 distribution, 근거·신뢰도 포함 proposed assertion 286건으로 변환했다. 12개 모두 `extracted`, manifest의 10개 자산 모두 pySHACL conform이며 원문 조각·근거 인용문·credential marker는 0건이다. 민감한 파일명과 locator가 포함된 결과는 Git 제외 로컬 `outputs/hyosung-voc-file-index-live.json`에만 보관한다.
- 증빙 테스트:
- `tests/test_file_knowledge.py::test_orchestrator_extractor_uses_strict_schema_and_persists_only_evidence_reference`
- `tests/test_file_knowledge.py::test_orchestrator_client_uses_sync_embeddings_endpoint`
- `tests/test_file_knowledge.py::test_local_pilot_deduplicates_content_and_writes_no_raw_text`
- `tests/test_file_knowledge.py::test_file_asset_api_requires_policy_and_redacts_jsonld_locator`
- `tests/test_file_knowledge.py::test_disksage_pre_copy_contract_enforces_metadata_precedence_and_evidence_binding`
- `tests/test_file_knowledge.py::test_disksage_pre_copy_contract_rejects_paths_and_unbounded_shape`
- `tests/test_file_knowledge.py::test_disksage_pre_copy_api_requires_create_policy_and_never_uses_graph_store`
- `tests/test_file_knowledge.py::test_disksage_pre_copy_api_redacts_validation_input_and_limits_body`
- `tests/test_graph_engine.py::test_config_loads_from_kv_mapping`

## 8) 다음 단계 (현재 브랜치에서 미반영 권고)

1. 조직 정책 기준으로 `search` 및 `list` 에 대한 사용 권한/발견성 정책을 명시적으로 강화
2. API level 감사 이벤트 보존 기간 및 위변조 방지(로그 저장소 정책) 적용
3. OpenCode/PR 리뷰 증적 저장(`PR`, `review`, `merge` 로그)과 main 병합 완료 상태 정기 기록

## 8) 구현 완료 증적(현재 HEAD 기준)
## 9) 구현 완료 증적(현재 HEAD 기준)

- 대상 브랜치: `codex/sdp-enterprise-foundation`
- 기준: `origin/main` 병합 후 현재 브랜치 HEAD
Expand Down
Loading
Loading