Skip to content

문서 신뢰 체인 백엔드 핸드오프

소유권과 경계

마이그레이션 20260718110617_document_trust_chain.sql의 owner는 platform-core/document-trust다. 계약관리 전용 하위 테이블이 아니라 모든 상품이 사용할 수 있는 플랫폼 능력이다.

  • 원본 상품은 불변 JSON snapshot과 source identity만 제공한다.
  • document_trust_requests는 원본 상품 테이블을 FK로 참조하지 않는다.
  • 원본 상품은 문서 신뢰 체인 없이도 commit을 완료한다.
  • 문서 신뢰 체인은 원본 상품 entitlement와 Office 권한만 확인한다.
  • PDF 생성, 타임스탬프, 전자문서 보관은 각각 별도 provider-neutral adapter다.

UI·public 문서·event payload에는 외부 사업자명이나 인증정보를 넣지 않는다.

데이터 모델

document_trust_requests

한 source revision의 문서 및 증명 상태를 소유한다. source identity는 (source_product_key, source_aggregate_type, source_aggregate_id, source_revision, document_kind)이며 Office 안에서 유일하다.

불변 영역은 Company/Office·관리코드, source identity와 snapshot, 문서 종류·제목·snapshot contract version, 타임스탬프·보관 요청 여부다.

text
generation_queued
  → pdf_ready
  → timestamp_queued → timestamped
  → archive_queued → archived
각 처리 상태 → failed → 같은 stage queued

PDF 결과는 storage key, 소문자 64자리 SHA-256, byte size, 생성시각을 함께 요구한다. 타임스탬프와 보관도 token/receipt storage key와 식별번호·완료시각이 모두 있어야 한다. 실패는 failure_stage, 안전한 오류 코드와 사용자용 메시지를 함께 가진다.

document_trust_events

request별 증가 sequence를 가진 append-only 이력이다. 생성, stage 완료·실패, retry를 기록한다. UPDATE/DELETE trigger가 이력 변조를 거부한다.

private.document_trust_commands

Create/Retry idempotency ledger다. (company_id, command_name, request_key) exact replay를 지원하며 다른 payload는 document_trust_idempotency_conflict다.

Public·worker 명령

  • create_document_trust_request(...): authenticated 관리자가 snapshot을 확정하고 PDF stage를 큐에 넣는다.
  • retry_document_trust_request(...): failed stage만 같은 adapter로 다시 큐잉한다.
  • private.record_document_trust_stage_result(...): service role worker 전용. expected revision과 현재 stage를 검증하고 다음 optional stage를 큐잉한다.

모든 adapter event는 platform integration outbox를 사용한다.

stageadapter key필수 성공 결과
PDFpdf-generation-v1storageKey, sha256, byteSize
타임스탬프timestamp-evidence-v1tokenStorageKey, serial
전자문서 보관electronic-archive-v1receiptStorageKey, archiveRecordKey

adapter 이름은 내부 capability 계약이며 특정 외부 사업자를 의미하지 않는다. adapter credential은 event/result JSON에 넣지 않는다.

Snapshot 계약

계약관리 최초 소비 계약은 contract-document.v1이다.

json
{
  "schemaVersion": "contract-document.v1",
  "issuer": { "companyId": "…", "companyName": "…", "officeId": "…", "officeName": "…" },
  "contract": { "id": "…", "managementCode": "…", "versionNumber": 2, "displayName": "…" },
  "parties": [{ "partyId": "…", "role": "customer", "displayName": "…", "snapshot": {} }]
}

재귀 sensitive-key 검사에서 provider/vendor/secret/credential/API key/token/password/certificate key 계열 key를 거부한다. worker result도 같은 검사를 거친다.

Storage와 다운로드

document-trust-private는 private bucket이다. worker만 service role로 업로드한다. object key는 다음 경로를 사용한다.

text
{company_id}/{office_id}/{request_id}/{artifact-file}

authenticated SELECT 정책은 경로의 세 scope가 request와 일치하고, 사용자가 해당 source product를 읽을 수 있을 때만 허용한다. 프론트는 60초 signed URL을 만들고 download filename을 지정한다. 서비스 역할 키는 브라우저에 절대 전달하지 않는다.

PDF 생성기

scripts/contract-document-pdf.mjscontract-document.v1 JSON을 읽고 production build의 EDS print CSS를 주입해 Chrome page.pdf()로 A4 PDF를 만든다. 생성 뒤 같은 byte의 SHA-256·byte size를 .evidence.json으로 출력한다.

bash
npm run build
npm run contract-document:pdf -- snapshot.json output.pdf

운영 worker도 동일 snapshot contract와 EDS print artifact를 사용해야 한다. PDF를 업로드한 뒤 byte를 다시 읽어 계산한 해시를 stage result에 전달한다.

검증

  • SQL smoke: supabase/tests/document_trust_chain_smoke.sql
  • migration 구조: src/composables/__tests__/documentTrustMigration.spec.js
  • repo 계약: src/composables/__tests__/documentTrustRepo.spec.js
  • PDF snapshot/escaping: src/composables/__tests__/contractDocument.spec.js

Smoke는 exact replay, direct write 차단, PDF→타임스탬프→보관 전이, 3개 adapter delivery, RLS, source product entitlement를 검사한다.