콘텐츠로 이동

AI로 SaaS 작성과 Desktop 연결

목표: AI가 저장한 프로젝트를 사람이 이어 작업하기

섹션 제목: “목표: AI가 저장한 프로젝트를 사람이 이어 작업하기”

AI에 프로젝트를 만들도록 요청할 때는 모델 이름만 받기보다 서버 프로젝트 식별자 → 모델 내용 → 검증 결과 → 저장한 판 → Desktop에서 이어 작업한 결과를 함께 확인하세요. 이름이 같다고 같은 프로젝트가 아니며 검증 성공이 앱 실행 성공을 뜻하지 않습니다.

학습 예시는 검토할 요구사항과 이를 구현할 클래스를 같은 SaaS 프로젝트에 준비하는 작업입니다. 현재 작성 모델 타입은 REQUIREMENTS와 CLASS입니다. 다른 모델 타입을 지원한다고 추정하지 않습니다.

기존 브라우저 로그인·공유는 SaaS 프로젝트와 공유, 기존 이동 파일의 가져오기·업데이트는 Desktop과 SaaS 연결에서 읽으세요. 이 페이지의 새 MCP·sync 계약과 기존 파일 이동 UI의 검증 범위는 다릅니다.

SaaS 작성 도구는 현재 인증된 세션과 CSRF를 사용하는 로컬 server adapter입니다. 일반 외부 AI 클라이언트에서 OAuth로 연결하는 설치 절차는 아직 제공하지 않습니다. 생산자 기본 설정에서도 MCP는 비활성입니다. 확인된 테스트 환경의 연결 안내를 받은 경우에만 이 계약을 검토하세요.

모든 호출에는 실제 tenantId가 필요하며 프로젝트 호출에는 반환된 projectId와 해당 도구의 projectKey를 사용합니다. 이를 AI가 임의로 만들거나 다른 프로젝트의 값으로 바꾸지 않습니다. 프로젝트 조회는 READ, 저장은 EDIT, 새 sync 다운로드는 별도 LOCAL_COPY 권한 검사를 받습니다. 공유의 독립 파일 권한도 확인하세요.

아래는 로컬 검증된 도구 계약을 읽는 순서입니다. 일반 사용자의 외부 MCP 연결·새 Desktop connector UI는 미완료입니다.

  1. 능력 확인: erweb.modeling.capabilities.get에서 모델 타입의 실제 native 초안을 읽습니다. 전체 모델 필드 설명서가 아니므로 없는 필드를 추측하지 않습니다.
  2. 프로젝트 준비: erweb.projects.list로 대상을 찾거나 erweb.project.create로 새 프로젝트를 요청합니다. 생성 요청의 requestId를 보존합니다.
  3. 현재 판 읽기: erweb.workspace.snapshot의 프로젝트 식별자, opaque expectedWorkspaceRevision, expectedDictionaryHash를 함께 사용합니다. 숫자 revision을 opaque 문자열 자리에 넣지 않습니다.
  4. 모델 준비: erweb.modeling.create로 REQUIREMENTS/CLASS를 준비하고 erweb.modeling.workspace.read로 해당 모델의 완전한 문서와 새 guard를 읽습니다. 기존 ID와 관련 없는 필드를 보존합니다.
  5. 내용 검증: erweb.modeling.workspace.validate로 typed 문서와 명시적 참조를 검사합니다. valid=false이면 내용을 수정하고 다시 검사합니다. 빈 object가 tool 입력 schema에 맞아도 실제 모델 검증이 통과한 것은 아닙니다.
  6. 저장하고 다시 읽기: erweb.modeling.workspace.apply에 현재 guard와 안정적인 requestId를 전달합니다. 성공 후 반환된 판·guard를 다시 읽어 예상한 모델이 저장됐는지 확인합니다.
  7. 연결과 코드 확인: erweb.traceability.get에서 자원·관계 식별자와 graph revision을 확인합니다. erweb.modeling.compile은 프로젝트의 정적 Unibase 분석이며 executionVerified=false입니다. 생성 앱을 빌드하거나 실행하는 도구가 아닙니다.

클래스 구현 내용은 실제 native 문서의 implementationLanguage=UNIBASE와 implementationBody를 기준으로 작성합니다. 필드 배치와 ID는 조회한 문서에서 읽어야 합니다. 아래 실제 합성 모델은 요구사항과 클래스 메서드를 ID로 연결하며, Desktop에서 본문을 수정한 뒤 서버 동기화와 fresh Desktop 재열기를 확인한 사례입니다.

실제 작성된 요구사항과 클래스 읽기

섹션 제목: “실제 작성된 요구사항과 클래스 읽기”

생산자 최종 합성 예제와 정적 분석의 원본 값을 표시합니다. 서로 다른 시점의 request 예제와 실제 acknowledgement를 곧바로 한 요청/응답으로 짝짓지 않습니다. 새 저장에는 새 snapshot을 읽으세요.

생산자가 제공한 합성 fixture의 실제 요구사항·클래스·정적 분석 source입니다. 모델 ID와 요구사항 참조를 연결해서 읽으세요.

확인할 내용원본 값
요구사항REQ-MCP-001: Desktop changed: return a stable synthetic greeting.
요구사항 IDmcp-requirement-hello
클래스Greeting
클래스의 요구사항 참조mcp-requirement-hello
package com.example.mcp;

public class Greeting {
    public string greet() {
return "Desktop synced";
    }
}

위 source의 상태는 ANALYZED, 범위는 PROJECT_STATIC_UNIBASE_ANALYSIS, executionVerified=false입니다. 정적 분석 source를 앱 실행 출력으로 표시하지 않습니다.

실제 native CLASS 문서 읽기
{
  "classes": [
    {
      "abstract": false,
      "description": "Receipt rollback fixture",
      "displayKey": "CLS-MCP-001",
      "enumValues": [],
      "fields": [],
      "final": false,
      "id": "mcp-class-greeting",
      "kind": "CLASS",
      "methods": [
        {
          "abstract": false,
          "description": "Return a fixture greeting",
          "id": "mcp-method-greet",
          "implementationBody": "return \"Desktop synced\";",
          "implementationLanguage": "UNIBASE",
          "name": "greet",
          "parameters": [],
          "returnType": "string",
          "static": false,
          "visibility": "PUBLIC"
        }
      ],
      "name": "Greeting",
      "notes": "Static analysis only",
      "order": 0,
      "packageId": "mcp-package-demo",
      "relatedRequirementIds": [
        "mcp-requirement-hello"
      ],
      "relatedResourceIds": [],
      "stereotype": "SERVICE",
      "typeParameters": []
    }
  ],
  "diagramGroups": [],
  "packages": [
    {
      "description": "Synthetic authored fixture",
      "displayKey": "PKG-MCP-001",
      "id": "mcp-package-demo",
      "name": "com.example.mcp",
      "order": 0
    }
  ],
  "relations": [],
  "schemaVersion": 1,
  "viewState": {
    "activeDiagramGroupId": "",
    "documentZoom": 1,
    "mode": "document",
    "selectedId": "",
    "selectedType": "",
    "tocWidth": 300
  }
}
합성 fixture의 apply 요청 읽기

예제의 guard는 캡처된 시점의 값입니다. 새 작업에는 현재 snapshot을 읽어야 합니다. 이 요청은 아래 과거 응답과 즉시 짝을 이루는 재실행 요청이 아닙니다.

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "erweb.modeling.workspace.apply",
    "arguments": {
      "tenantId": "ab639219-1a96-4c16-b4cd-d43f873a3ea2",
      "projectId": "e6cf5971-d8d7-4def-ae8e-3fed4993cb56",
      "projectKey": "8650570a-93a5-42cc-95bf-624fd8cdb0d2",
      "expectedWorkspaceRevision": "d61c4e6761c15c4b6a04fe243a334874811ea9a5a4b66974f4c244b1701d33c6",
      "expectedDictionaryHash": "45f688f4b70295760be9301a395d80a03524fc7064a180830ba95cc301f625b6",
      "requestId": "example-client-apply-0001",
      "modelingId": "108d9f4f-a49d-a023-d107-91e1ca7e9283",
      "workspace": {
        "classes": [
          {
            "abstract": false,
            "description": "Receipt rollback fixture",
            "displayKey": "CLS-MCP-001",
            "enumValues": [],
            "fields": [],
            "final": false,
            "id": "mcp-class-greeting",
            "kind": "CLASS",
            "methods": [
              {
                "abstract": false,
                "description": "Return a fixture greeting",
                "id": "mcp-method-greet",
                "implementationBody": "return \"Desktop synced\";",
                "implementationLanguage": "UNIBASE",
                "name": "greet",
                "parameters": [],
                "returnType": "string",
                "static": false,
                "visibility": "PUBLIC"
              }
            ],
            "name": "Greeting",
            "notes": "Static analysis only",
            "order": 0,
            "packageId": "mcp-package-demo",
            "relatedRequirementIds": [
              "mcp-requirement-hello"
            ],
            "relatedResourceIds": [],
            "stereotype": "SERVICE",
            "typeParameters": []
          }
        ],
        "diagramGroups": [],
        "packages": [
          {
            "description": "Synthetic authored fixture",
            "displayKey": "PKG-MCP-001",
            "id": "mcp-package-demo",
            "name": "com.example.mcp",
            "order": 0
          }
        ],
        "relations": [],
        "schemaVersion": 1,
        "viewState": {
          "activeDiagramGroupId": "",
          "documentZoom": 1,
          "mode": "document",
          "selectedId": "",
          "selectedType": "",
          "tocWidth": 300
        }
      }
    }
  }
}
서로 다른 시점의 실제 저장 응답 읽기

모델 작성 뒤의 응답과 Desktop 수정본 동기화 뒤의 응답입니다. 두 단계의 판·접근 판·사전 hash·projectKey를 같은 값으로 고정하지 않습니다.

{
  "requestId": "authored-class-0001",
  "replayed": false,
  "result": {
    "tenantId": "ab639219-1a96-4c16-b4cd-d43f873a3ea2",
    "projectId": "e6cf5971-d8d7-4def-ae8e-3fed4993cb56",
    "projectKey": "95be3596-e5af-a7a6-5561-0522c1b6f6f2",
    "revision": 7,
    "accessRevision": 1,
    "sourceRevision": "ccaedaf25b9bb50908609beccd0991ef126acdec4637c0f9a630c8c0f2aba1dd",
    "dictionaryHash": "e5d1f1cce8b27ed8447418c9e4f70f2c3d471da930e5e3c32747363564a06fd2",
    "expectedWorkspaceRevision": "d9f96accff471172a728882ef36dfd4b45b3d349e939dffc4d9372cce8a64c2c",
    "expectedDictionaryHash": "e5d1f1cce8b27ed8447418c9e4f70f2c3d471da930e5e3c32747363564a06fd2",
    "transferId": "95be3596-e5af-a7a6-5561-0522c1b6f6f2",
    "dictionaries": [
      {
        "file": "dictionary-1.erwebdict",
        "fileSetId": 1,
        "setId": 1,
        "sha256": "e878aaeb6adcf88df166aff0263c7b9c4de1c71a3a0f88e2566785b7ed392b65"
      }
    ],
    "projectAccess": {
      "role": "OWNER",
      "canEdit": true,
      "canDownload": true,
      "canLocalCopy": true,
      "accessRevision": 1
    },
    "modelingId": "108d9f4f-a49d-a023-d107-91e1ca7e9283",
    "workspaceSha256": "626dac798a6f9dea0f62f643c92249f0a21d68bc41f1a632103bc91b3af5789c"
  }
}
{
  "requestId": "desktop-sync-roundtrip-0001",
  "replayed": false,
  "result": {
    "tenantId": "ab639219-1a96-4c16-b4cd-d43f873a3ea2",
    "projectId": "e6cf5971-d8d7-4def-ae8e-3fed4993cb56",
    "projectKey": "8650570a-93a5-42cc-95bf-624fd8cdb0d2",
    "revision": 11,
    "accessRevision": 3,
    "sourceRevision": "823f3278063c786e8b386cc6edacedcc9ac7ea554ac868cb3e78b64b554772f0",
    "dictionaryHash": "45f688f4b70295760be9301a395d80a03524fc7064a180830ba95cc301f625b6",
    "expectedWorkspaceRevision": "d61c4e6761c15c4b6a04fe243a334874811ea9a5a4b66974f4c244b1701d33c6",
    "expectedDictionaryHash": "45f688f4b70295760be9301a395d80a03524fc7064a180830ba95cc301f625b6",
    "transferId": "95be3596-e5af-a7a6-5561-0522c1b6f6f2",
    "dictionaries": [
      {
        "file": "dictionary-2.erwebdict",
        "fileSetId": 2,
        "setId": 2,
        "sha256": "c72dffcbd33d8e505c3c549b6adbdd355cf73047703008eef0559834e677761d"
      }
    ],
    "projectAccess": {
      "role": "OWNER",
      "canEdit": true,
      "canDownload": true,
      "canLocalCopy": true,
      "accessRevision": 3
    }
  }
}

JSON-RPC의 id는 응답을 맞추는 값입니다. 저장의 requestId는 동일한 작업을 식별합니다. 저장 key는 8~128자의 영문·숫자·밑줄·하이픈을 받습니다. 프로젝트와 테넌트 식별자는 서버가 제공한 36자 lowercase canonical UUID를 사용합니다. 최종 tool schema와 구현은 같은 길이·pattern을 검사하며, 생산자는 경계값 생성/replay와 잘못된 ID의 실제 요청 거절을 확인했습니다.

쓰기 응답은 먼저 result.isError를 확인합니다. 성공한 structuredContent는 {requestId, replayed, result} envelope이며, 실제 projectId·revision과 모델 저장의 guard는 그 안의 result에 있습니다. 읽기 응답은 structuredContent에서 데이터를 직접 읽습니다. HTTP 200만 보고 저장 완료로 판단하지 않습니다.

응답을 잃은 같은 저장 작업은 동일 requestId와 원래 인수 전체로 재시도합니다. 성공한 재시도는 replayed=true로 원래 acknowledgement를 반환하는 계약입니다. 같은 key로 내용을 바꾸면 conflict입니다. 내용이나 guard를 바꾼 새 작업은 새 requestId를 사용합니다. 프로젝트·권한·사전 conflict가 나면 새 snapshot을 읽고 변경을 다시 검토합니다.

현재 slice에는 delete 도구가 없습니다. 기존 작성 ID를 빼서 삭제하는 apply/sync는 삭제·복원 정책 확정 전 차단됩니다. 권한 회수 후 새 쓰기와 기존 receipt 읽기도 현재 ACL 검사를 받으며, 이미 전달된 로컬 파일을 회수하는 절차는 아닙니다.

Desktop 처음 내려받기와 이후 동기화

섹션 제목: “Desktop 처음 내려받기와 이후 동기화”
원하는 작업 새 서버 계약에서 확인할 것 현재 확인 범위
현재 서버 내용 읽기 sync snapshot의 프로젝트·내용·접근 판과 사전 guard 생산자 실제 HTTP/native 조회 통과
Desktop 최초 로컬 사본 LOCAL_COPY와 정확한 project/access revision으로 guarded download 실제 native COPY·내용 조회 통과, 새 connector UI 미완료
Desktop 수정본 반영 EDIT·원본 transferId 계보·현재 guard·bundle bytes/hash·안정적인 requestId로 sync update 실제 Desktop 편집/export→HTTP sync→fresh Desktop 통과
양쪽 수정 충돌 새 snapshot에서 차이를 검토 자동 병합 제공 없음

서버 프로젝트 UUID와 작성 자원 ID, 참조 사전·자산을 대조해야 합니다. Desktop COPY의 로컬 등록 ID·projectKey·namespace context는 다시 묶일 수 있으므로 같은 숫자나 같은 context key가 유지된다고 추정하지 않습니다. 원래 transferId의 provenance와 서버가 반환한 최신 scope/guard를 확인합니다. 연결 등록의 상세 UI 계약과 삭제·복원은 후속 확인 사항입니다.

SaaS AI 작성 내용을 편집하고 재열어 확인한 실제 Desktop CLASS 화면

합성 프로젝트의 실제 Desktop 0.9.4 화면입니다. Greeting 메서드의 Unibase 본문과 요구사항 참조를 확인한 화면이며 새 sync connector UI 화면은 아닙니다. 검증 executable의 source commit은 build stamp로 확인되지 않았습니다.

같은 업로드의 replay는 판을 추가하지 않았고, 별도 stale 요청은 conflict로 거절됐습니다. 모델 문서·ID·판·시간, 참조 사전 bytes, trace ID/관계의 보존도 비교했습니다. 정상 앱 재시작 후 완료 receipt의 유지와 SQL 실패 rollback은 실제 검증됐지만 commit 중 강제 종료·전원 손실·복구는 미검증입니다. 현재 HTTP RESTORE는 지원되지 않습니다. 검증된 재시도나 재열기를 복원 기능으로 확대하지 않습니다.

서비스 라이브러리 조회와 작성 도구 구분하기

섹션 제목: “서비스 라이브러리 조회와 작성 도구 구분하기”

Mail 첫 계약의 공통 타입·profile·제약과 Unibase 조회 bridge는 라이브러리를 읽는 계약입니다. 이 페이지의 SaaS 도구는 인증된 서버 프로젝트를 읽거나 저장합니다. 이름과 입력을 서로 바꾸어 사용하지 않습니다.

SaaS capability는 같은 Mail metadata를 읽지만 고정 worker의 새 Mail 컴파일·export와 runtime scope 연결은 NOT_CONNECTED입니다. 목록에 보인다는 이유로 SaaS에서 mail 코드를 실행할 수 있다고 판단하지 않습니다. storage·파일 전송·billing·preview identity는 계속 미구현입니다.

2026-10-07 08:19 UTC에 정정된 제품 미리보기 설계는 Market에서 새 탭으로 진입하고 SaaS가 앱·SQLite DB·세션 runtime·provider를 실행하는 흐름입니다. Unibase는 host에 종속되지 않는 export·generator·SDK를 제공합니다. 이 별도 연결 계약은 조율 중이며 아래의 모델 작성·Desktop 왕복 QA로 완료를 판정하지 않습니다. 역할과 시뮬레이션 원칙을 함께 확인하세요.

다음 11개 도구와 입력 schema는 생산자가 인증된 격리 환경에서 받은 실제 tools/list snapshot입니다. mode는 SAAS_SCOPED_AUTHORING_V1이며 Desktop의 활성 세션 입력과 구분합니다. 이 문서에서 도구를 호출하거나 프로젝트를 저장하는 버튼을 제공하지 않습니다.

erweb.modeling.capabilities.get

Actual native default/document capabilities; partial schema, not full Desktop catalog parity.

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "tenantId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "modelingType": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "enum": [
        "REQUIREMENTS",
        "CLASS"
      ]
    }
  },
  "required": [
    "tenantId",
    "modelingType"
  ]
}
erweb.projects.list

List owned SaaS projects.

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "tenantId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    }
  },
  "required": [
    "tenantId"
  ]
}
erweb.project.create

Create a server project using the trusted native seed, with durable retry identity.

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "tenantId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "requestId": {
      "type": "string",
      "minLength": 8,
      "maxLength": 128,
      "pattern": "^[A-Za-z0-9_-]{8,128}$"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 256
    },
    "description": {
      "type": "string",
      "minLength": 0,
      "maxLength": 65536
    },
    "dictionaryName": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "namingPolicyId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "enum": [
        "general-spaced-v1",
        "custom-compact-v1",
        "public-standard-v1"
      ]
    }
  },
  "required": [
    "tenantId",
    "requestId",
    "name"
  ]
}
erweb.workspace.snapshot

Read the routed server project and opaque content/access/dictionary guards.

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "tenantId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    }
  },
  "required": [
    "tenantId",
    "projectId"
  ]
}
erweb.modelings.list

Read project model inventory.

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "tenantId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectKey": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    }
  },
  "required": [
    "tenantId",
    "projectId",
    "projectKey"
  ]
}
erweb.modeling.create

Create REQUIREMENTS or CLASS with current snapshot guards.

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "tenantId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectKey": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "expectedWorkspaceRevision": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "expectedDictionaryHash": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "requestId": {
      "type": "string",
      "minLength": 8,
      "maxLength": 128,
      "pattern": "^[A-Za-z0-9_-]{8,128}$"
    },
    "type": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "enum": [
        "REQUIREMENTS",
        "CLASS"
      ]
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 256
    },
    "description": {
      "type": "string",
      "minLength": 0,
      "maxLength": 65536
    }
  },
  "required": [
    "tenantId",
    "projectId",
    "projectKey",
    "expectedWorkspaceRevision",
    "expectedDictionaryHash",
    "requestId",
    "type",
    "name"
  ]
}
erweb.modeling.workspace.read

Read the complete native workspace and fresh server guards.

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "tenantId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectKey": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "modelingId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    }
  },
  "required": [
    "tenantId",
    "projectId",
    "projectKey",
    "modelingId"
  ]
}
erweb.modeling.workspace.validate

Native typed document/reference validation; no persistence or execution.

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "tenantId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectKey": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "expectedWorkspaceRevision": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "expectedDictionaryHash": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "modelingId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "workspace": {
      "type": "object"
    }
  },
  "required": [
    "tenantId",
    "projectId",
    "projectKey",
    "expectedWorkspaceRevision",
    "expectedDictionaryHash",
    "modelingId",
    "workspace"
  ]
}
erweb.modeling.workspace.apply

Persist a complete typed document; existing authored IDs cannot be removed in this slice.

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "tenantId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectKey": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "expectedWorkspaceRevision": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "expectedDictionaryHash": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "requestId": {
      "type": "string",
      "minLength": 8,
      "maxLength": 128,
      "pattern": "^[A-Za-z0-9_-]{8,128}$"
    },
    "modelingId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "workspace": {
      "type": "object"
    }
  },
  "required": [
    "tenantId",
    "projectId",
    "projectKey",
    "expectedWorkspaceRevision",
    "expectedDictionaryHash",
    "requestId",
    "modelingId",
    "workspace"
  ]
}
erweb.modeling.compile

Static project Unibase inspection; executionVerified=false, no target/command.

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "tenantId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectKey": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "expectedWorkspaceRevision": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "expectedDictionaryHash": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "modelingId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    }
  },
  "required": [
    "tenantId",
    "projectId",
    "projectKey",
    "expectedWorkspaceRevision",
    "expectedDictionaryHash",
    "modelingId"
  ]
}
erweb.traceability.get

Read explicit project resource IDs, relations and graph revision.

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "tenantId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectId": {
      "type": "string",
      "minLength": 36,
      "maxLength": 36,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "projectKey": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    }
  },
  "required": [
    "tenantId",
    "projectId",
    "projectKey"
  ]
}

이 최종 snapshot 소비 검사에서는 필수 필드·문자열 길이·pattern·enum·추가 필드 거절과 mode를 확인합니다. 전체 native 모델 내용, 현재 권한, 서버 CAS·DB 저장·실행 검증은 이 입력 schema 검사를 대신할 수 없습니다.

안내된 SaaS scope에서 요구사항과 클래스를 준비하세요. 실제 capability와 workspace를 먼저 읽고 ID·사전 참조를 보존하세요. 완전한 문서를 검증한 뒤 저장한 프로젝트 식별자·판·검증 결과를 보여 주세요. 정적 분석과 실행은 구분하고, Desktop 첫 다운로드와 이후 동기화에서 아직 검증되지 않은 부분을 표시하세요.

사용자는 저장 결과와 연결된 자원·관계를 확인한 뒤 Desktop으로 이어갈지를 검토합니다. 실패하면 requestId·오류 code·확인한 판을 보존하고 다시 읽으세요. 세션 cookie·CSRF·자격증명은 문서나 공유용 검토 결과에 넣지 않습니다. 현재 불확실한 왕복·충돌·복원 부분을 AI가 성공 설명으로 채우지 않도록 요청하세요.