본문으로 건너뛰기

파일 형식과 저장

Report Workbench와 Python SDK의 px.Report는 같은 .pxreport revision을 만듭니다. Python SDK의 px.Flow와 PXFLOW Studio는 .pxflow revision을 만듭니다. Runtime은 Report가 고정한 Flow revision 또는 Studio·SDK에서 선택한 Flow revision을 실행해 immutable Result를 만듭니다.

형식·객체역할편집 원본
.pxreport본문, authored value·table, Flow connection과 result 표시 위치를 담는 Report direct objectReport Workbench 또는 Python SDK의 px.Report
.pxflowNode, binding, Group, interface와 Node setting을 담는 Flow direct objectPXFLOW-native Flow
SDK source + lockFunction, Component와 SDK-linked Flow 작성 원본Python source
package/container releaseFunction·Component descriptor, code와 dependency 배포. signature는 optional envelope이고 RFC-005 verification을 통과한 release만 verified라고 부름package source
.pxproject여러 Flow, SDK source, dependency와 선택 Result의 portable container포함된 각 root
Run / ResultSnapshotpinned Flow 실행 기록과 immutable resultRuntime ledger/object store
Node artifact현재 지원되는 first-party Node가 만든 output; third-party Component artifact는 contract-only편집 원본이 아닌 immutable artifact

report = px.Report(...)로 만든 Report 객체에서 report.save()를 호출하면 canonical .pxreport revision이 만들어집니다. Workbench, SDK와 AI/MCP는 이 revision에 같은 typed Report command를 적용해 다음 revision을 만듭니다. SDK-linked Flow의 계산 source는 Python에 유지되며 Studio의 지원 편집은 source-safe diff로 반영됩니다.

공통 canonical 규칙

  • UTF-8과 명시된 schema version을 사용합니다.
  • semantic key는 종류별 lower_snake_case입니다.
  • key 순서, set 정렬, number와 unit 표현을 canonical writer가 고정합니다.
  • unknown union kind와 허용되지 않은 field는 거부합니다.
  • schema가 ordered로 선언한 Report block, typed field, typed List item과 command 배열은 입력 순서를 보존하고 digest에 포함합니다. membership·capability처럼 set으로 선언한 배열만 stable key로 정렬합니다.
  • label, canvas 좌표와 단순 표시 순서는 identity로 사용하지 않습니다. ordered value의 의미 있는 순서를 버리거나 set처럼 다시 정렬한다는 뜻은 아닙니다.
  • secret, access token, local permission handle과 mutable latest reference를 portable bytes에 저장하지 않습니다.
  • immutable revision과 package release는 content digest로 검증합니다.

Python binding의 public codec은 현재 구현된 px.loads(data: bytes) -> objectpx.dumps(document) -> bytes 두 pure operation입니다. public SDK wrapper가 private native adapter를 통해 Canonical Core codec을 호출하며, crates/pxflow-python의 저수준 binding 이름은 public API가 아닙니다. codec에는 path·file·stream helper, lock, atomic save와 revision commit을 넣지 않고 Host가 소유합니다. 지원 direct document union과 exact writer member order는 owning RFC가 승인한 schema/fixture만 사용하며 legacy YAML·manifest를 추측하지 않습니다. V1은 schemaVersion: 1이 최초 production 포맷이므로 migration 대상이 되는 이전 production 버전이 없습니다. 지원하지 않는 버전은 안정적인 PX_SCHEMA_UNSUPPORTED_VERSION 진단으로, legacy 포맷은 안정적인 PX_DOCUMENT_LEGACY_FORMAT 진단으로 거부하며, 두 경우 모두 read-only recovery를 제공하고, previous→current migration은 최초 production schema bump부터 적용합니다.

현재 Python codec은 .pxflow와 위 저장 계약의 .pxreport를 읽고 씁니다. Report의 반환형은 불변 px.ReportDocument이며 to_dict()는 복사본입니다. report.document()는 소유 Client로 정확한 저장 revision을 읽고 미저장 편집을 거부합니다. px.dumps(report.document())로 저장 bytes를 얻을 수 있습니다. Report 파일의 unsupported version은 structured diagnostic으로 거부하며 Flow의 recovery 객체로 변환하지 않습니다.

client.export_documents(path, flows={...}, reports={...})는 SDK의 파일 작업입니다. 선택된 문서·프로젝트 Function·Report attachment와 document-export.json을 기록합니다. 기존 폴더는 덮어쓰지 않으며 전체 수집·검증 뒤 폴더를 게시합니다. 외부 dependency는 정확한 pin으로 남기고 설치나 실행은 하지 않습니다. .pxproject archive/import와는 다른 문서 모음이며 referenced external package와 Run ledger 전체를 포함하지 않습니다.

.pxreport top-level

이 절은 .pxreport revision root V1의 규범적 사람 권위입니다. Report의 direct object는 문서 구조와 Flow 연결을 함께 저장하며, 다음 13개 member만 이 순서로 허용합니다.

json
{
  "schemaVersion": 1,
  "flowConnectionsSchemaVersion": 1,
  "metadata": {
    "name": "girder_review",
    "title": "Girder review"
  },
  "bodyDocument": {
    "schemaVersion": "1.0.0",
    "doc": {
      "type": "doc",
      "content": [
        { "type": "paragraph" }
      ]
    }
  },
  "blockAnchors": ["introduction"],
  "values": {},
  "tables": {},
  "attachments": {},
  "inputProjections": {},
  "resultSlots": {},
  "resultTables": {},
  "artifactSlots": {},
  "flowConnections": []
}

schemaVersionflowConnectionsSchemaVersion은 각각 JSON integer 1만 지원하며 서로 독립적으로 dispatch합니다. 둘 중 하나가 없거나 integer가 아니거나 지원 집합 밖이면 PX_SCHEMA_UNSUPPORTED_VERSION으로 거부하고 원본 bytes를 보존한 read-only recovery만 허용합니다. 이 상태에서는 편집, 실행과 저장을 허용하지 않습니다.

지원 여부는 현재 버전의 필수 필드·unknown field 검사보다 먼저 판단합니다. 새 버전의 필드를 삭제하도록 안내하지 않습니다. Python px.loads()의 Report 버전 거부는 기존 ValueErrordiagnostic bytes를 유지하며, original_bytes에 입력 bytes를 그대로 보존합니다. 이 오류 객체는 편집·실행·저장 가능한 Report가 아닙니다. 향후 명시적 변환 기능은 새 revision을 만들며 기존 revision과 원본 bytes를 덮어쓰지 않습니다.

이 두 버전은 portable container envelope의 major.minor string formatVersion(예: "1.0") 및 bodyDocument.schemaVersion: "1.0.0"과 별개의 버전 도메인입니다. 미래 pageTemplates는 V1 root member가 아니며, 별도 버전을 가진 additive root change와 명시적 migration·rollback을 동반할 때만 추가할 수 있습니다. Unknown root member를 현재 V1 의미로 추측하지 않습니다.

valuestables가 authored value를 소유합니다. flowConnections는 Report source를 Flow public input에 연결하고 Flow public result를 Report 위치에 표시하는 mapping을 소유합니다. Flow 내부 Node와 binding은 .pxreport에 복제하지 않습니다.

json
{
  "connectionKey": "girder_check",
  "mappingGeneration": 3,
  "flowTarget": {
    "scope": {
      "kind": "project",
      "workspaceKey": "structural_team",
      "projectKey": "bridge_design"
    },
    "flowKey": "girder_check",
    "revisionRef": "sha256:flow-revision-digest"
  },
  "inputMappings": [
    {
      "mappingKey": "span_input",
      "portKey": "span",
      "source": {
        "kind": "reportValue",
        "blockKey": "design_basis",
        "fieldKey": "span"
      }
    }
  ],
  "resultMappings": [
    {
      "mappingKey": "utilization_result",
      "portKey": "utilization",
      "target": {
        "kind": "reportResultSlot",
        "blockKey": "check_summary",
        "slotKey": "utilization"
      }
    }
  ],
  "runPolicy": "manual"
}
  • flowTarget.revisionRef는 exact immutable Flow revision을 고정합니다.
  • mappingKey는 connection의 input/result mapping 전체에서 유일하고, mappingGeneration은 mapping 의미가 바뀔 때 단조 증가합니다.
  • input source와 result target은 stable Report semantic key를 사용합니다.
  • authored value는 Report block/cell이, 실행 결과는 immutable ResultSnapshot이 소유합니다.
  • mapping은 value bytes, Flow 내부 binding, DOM path와 화면 좌표를 저장하지 않습니다.
  • runPolicy의 V1 closed set은 manual 하나뿐입니다. 연결 저장, binding 변경과 Report 저장은 Run을 시작하지 않으며 명시적 Runtime command만 실행을 시작합니다.

전체 closed union과 validation은 Report와 Flow 공개 포트 연결을 따릅니다.

FunctionDescriptor

explicit stable key를 받은 @px.function(key="girder_resistance", ...)을 materialize하면 FunctionDescriptor가 생성됩니다. Python symbol rename은 이 key를 바꾸지 않습니다.

json
{
  "schemaVersion": 1,
  "functionKey": "girder_resistance",
  "version": "4.0.0",
  "label": "Girder resistance",
  "description": "Checks the design resistance.",
  "inputs": [
    {
      "portKey": "section",
      "type": {"kind": "record", "recordKey": "section"},
      "connection": true
    }
  ],
  "results": [
    {
      "portKey": "utilization",
      "type": {"kind": "float64"},
      "unit": "1",
      "connection": true
    }
  ],
  "executionPolicy": {
    "deterministic": true,
    "sideEffect": "none"
  },
  "capabilities": []
}

Descriptor는 실행 code를 복제하지 않고 exact package release를 가리킵니다. signature가 있는 release도 RFC-005 verification을 통과한 channel에서만 verified라고 부릅니다. Canvas에 배치되기 전 Function 정의이며, flow.node(...) placement가 Function Reference Node instance를 만듭니다.

ComponentDescriptor

explicit stable key를 가진 @px.component(key="model_analysis", ...)는 현재 process-local ComponentDescriptor metadata를 등록합니다. portable declaration/package compiler는 별도 계획 계약입니다. Python symbol은 import projection이고 componentKey identity를 대신하지 않습니다. Project가 소유하는 Python 계산도 @px.function(key="...", ...)으로 등록하며 materialize할 때 같은 FunctionRef target을 사용합니다. Decorator 없는 일반 Python def는 Function·Component 구현 내부 helper이고 Node target으로 직렬화하지 않습니다.

json
{
  "schemaVersion": 1,
  "componentKey": "model_analysis",
  "componentContractVersion": 1,
  "label": "Model analysis",
  "description": "Runs a structural model analysis.",
  "classification": "flow-node",
  "category": "calculate",
  "subcategory": "structural",
  "inputs": [
    {
      "portKey": "model",
      "type": {"kind": "artifact"},
      "required": true,
      "nullable": false
    }
  ],
  "results": [
    {
      "portKey": "analysis",
      "type": {"kind": "record", "recordKey": "analysis_results"},
      "nullable": false
    }
  ]
}

ComponentDescriptor는 generated catalog metadata이며 package, presentation 또는 Node placement identity를 담지 않습니다. 아래 content/component.json의 packaged V1 선언과 같은 object로 해석하지 않습니다. packaged V1 선언은 typed port, executor entry와 선언형 presentation.icon/summaryResultKeys를 함께 보존합니다. 현재 production Flow reader는 선언→renderer bridge가 없어 이를 그리지 않고 Component target을 read-only unsupported로 표시합니다. 미래 다중 Component V2의 분류·격리·성능·migration 계약은 Component container V2와 서드파티 Node UI를 따릅니다.

V1 wire reader와 내부 fixture 호환성은 production install permission이 아닙니다. 현재 installer는 executor bytes와 exact ProjectScope contribution row를 바로 reachable하게 만들며 안전한 inactive quarantine이 없습니다. data-path isolation은 구현됐지만 Project authorization과 execution-trust closure가 닫히기 전에는 production third-party Component install admission과 activation을 차단합니다. 두 production gate의 구현 증거도 아직 없으므로 current production workflow로 문서화하지 않습니다.

PXFLOW top-level

이 절은 .pxflow direct document V1의 규범적 사람 권위입니다. 닫힌 Draft 2020-12 machine schema는 pxflow-direct.v1.schema.json이며 두 권위의 top-level member 목록, 순서와 requiredness는 npm run docs:contract가 양방향으로 검사합니다.

Canonical writer는 다음 schema-owned 순서로 top-level member를 기록합니다.

ordermemberrequired
1schemaVersionrequired
2flowKeyrequired
3labelrequired
4descriptionrequired
5typeDefinitionsoptional
6inputsrequired
7resultsrequired
8nodesrequired
9groupsrequired
10externalSubflowsrequired
11presentationrequired
12dependenciesrequired
13authoringrequired

열두 core member는 모두 항상 required입니다. typeDefinitions만 기존 V1 revision 호환성을 위해 optional이며, 값이 없는 array는 [], 값이 없는 object는 {}로 기록하고 core member omission은 invalid입니다. 이 규칙은 canonical bytes와 digest를 결정적으로 유지하고, writer가 field를 빠뜨리면 즉시 실패하게 하며, reader가 default를 추론하지 않게 하고, Rust/WASM/Python/TypeScript 네 binding의 default 처리가 서로 달라지는 것을 막습니다.

Human-readable canonical bytes는 UTF-8 JSON, BOM 없음, LF, two-space indent와 final newline 한 개를 사용합니다. 위 member order는 schema가 소유하며 writer가 임의로 재정렬하지 않습니다. Semantic-digest preimage는 human-readable bytes와 별도로 RFC 8785 JCS를 적용합니다.

Semantic key와 sha256:<64 lowercase hex> content-digest ref는 accepted RFC-002, TypeDescriptor의 sixteen-kind closure와 bounds는 accepted RFC-003, package release reference는 accepted RFC-005를 그대로 사용합니다. 이 절은 그 계약을 다시 정의하거나 좁히지 않습니다. UUID는 content-digest ref 위치나 portable .pxflow wire 어디에도 기록하지 않습니다.

Flow input 선언

inputs[]requiredconstraints는 기존 문서를 깨뜨리지 않는 optional member입니다. required가 없으면 true입니다. 기존 실행 문서와 계획이 input을 required로 설명하고 승인된 flow.input(input_key, ValueType, *, description, unit=None, constraints=None) signature에는 requiredness 인자가 없으므로, omission을 optional로 바꾸면 과거 Flow의 실행 의미가 조용히 넓어집니다. Python authoring은 따라서 required를 생략하며 required input을 만들고, direct document가 명시적으로 required: false를 저장한 경우에만 Run payload에서 그 key를 생략할 수 있습니다.

승인된 constraint grammar v1의 predicate kind 목록은 allowedValues 하나입니다.

json
{
  "version": 1,
  "allowedValues": ["2", "3"]
}

allowedValues는 비어 있지 않은 RFC-003 typed value 목록이고, RFC 8785 canonical byte가 같은 중복 값은 허용하지 않되 author order에는 의미를 부여하지 않습니다. Runtime은 제출 값의 type·unit을 확인한 뒤 같은 canonical value가 목록에 있는지 exact saved Flow revision에 대해 한 번 판정합니다. 저장소의 승인된 문서와 prototype을 실측했을 때 constraint predicate 문법은 없었고, prototype에는 artifact mediaTypes 예시만 있었습니다. Artifact Run 값은 content digest뿐이라 core가 media type을 다시 추측할 수 없으므로 그 예시는 이 grammar로 승격하지 않았습니다. 이 최소 목록은 schema, core와 contract lock을 함께 갱신할 때만 version 1 안에서 additively 늘립니다. 임의 expression, regex, Python/JavaScript 조각과 실행 코드는 허용하지 않습니다.

Flow record definition closure

RFC-003은 recordKey가 소유 interface의 typeDefinitions closure에서 resolve된다고 이미 정합니다. Direct Flow에서는 그 소유자가 exact saved Flow revision이므로, 외부 moving registry나 package head를 참조하지 않고 optional top-level typeDefinitions.records[] 안에 definition을 함께 저장합니다.

json
{
  "typeDefinitions": {
    "records": [
      {
        "recordKey": "load_case",
        "fields": [
          {
            "fieldKey": "case_key",
            "type": { "kind": "string" },
            "required": true,
            "nullable": false
          },
          {
            "fieldKey": "magnitude",
            "type": { "kind": "float64" },
            "required": true,
            "nullable": false
          }
        ],
        "rowKeyField": "case_key"
      }
    ]
  }
}

Closure가 있으면 Flow input/result interface에서 도달하는 모든 recordKey가 그 안에서 resolve되고, Run input record는 exact field 이름, 각 field TypeDescriptor, required와 nullable을 따릅니다. Closure가 없는 기존 V1 revision은 계속 읽고 실행할 수 있으며, 그 exact revision의 record 검증은 기존 의미대로 JSON object 경계까지만 수행합니다. Runtime 값에서 definition을 역추론하거나 나중 revision의 closure를 빌려오지 않습니다.

json
{
  "schemaVersion": 1,
  "flowKey": "girder_review",
  "label": "Girder review",
  "description": "Calculates a girder check.",
  "inputs": [],
  "results": [],
  "nodes": [],
  "groups": [],
  "externalSubflows": [],
  "presentation": {
    "nodes": {}
  },
  "dependencies": [],
  "authoring": {
    "mode": "pxflowNative"
  }
}

Node

Node도 V1 schema가 소유하는 규범적 direct object입니다. Writer는 nodeKey, target, inputBindings 순서로 required member를 기록합니다. settings는 Component target에만 허용되는 optional 마지막 member입니다. Function target에는 settings를 기록하지 않습니다.

json
{
  "nodeKey": "resistance_check",
  "target": {
    "kind": "function",
    "publisher": "pipelinexlab",
    "packageKey": "structural_checks",
    "packageVersion": "2.3.0",
    "packageDigest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "releaseByteDigest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    "descriptorDigest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
    "functionKey": "girder_resistance",
    "functionVersion": "4.0.0"
  },
  "inputBindings": {
    "section": {
      "kind": "flowInput",
      "portKey": "section"
    }
  }
}

Node target은 closed union입니다.

target kind의미
functioninstalled package가 제공한 exact signed Function release
projectFunctionProject가 소유한 Function의 immutable content-addressed version
componentV1 saved artifact/internal fixture가 보존하는 exact 7-field Component release identity. current accepted unsigned row는 absent·not-checked이고 signed input은 trust authority 없이 기록하지 않습니다. marketplace verification을 통과한 release만 verified라고 부르며 production third-party install·activation은 차단합니다

같은 Project의 @px.function definition을 배치하면 projectFunction target에 explicit functionKeysha256: versionRef를 기록합니다. Installed package의 Function은 Project dependency closure가 resolve한 exact release reference가 있을 때만 배치합니다. Component target shape는 기존 V1 bytes와 내부 fixture에서 보존하지만, production third-party Component placement는 위 isolation·trust closure 뒤에만 엽니다.

Regular Node target union은 위 세 kind로 닫힙니다. Imported Flow는 regular Node가 아니라 아래 externalSubflows[]에 저장합니다. Component target만 schema-valid explicit settings를 가집니다. Function Reference는 owning definition의 계약을 현재 Node에서 바꾸지 않습니다. fixed port를 Node가 덮어쓰지 않습니다. dynamicInputs와 그 instance port materialization은 현재 packaged Component V1 grammar에 없으며 별도 versioned contract 전에는 사용하지 않습니다.

Input binding

json
{
  "kind": "nodeResult",
  "nodeKey": "resistance_check",
  "portKey": "result"
}

Binding은 destination Node의 inputBindings[portKey]에 저장합니다. Report와 Flow public port의 관계는 여기에 넣지 않고 .pxreport.flowConnections에 저장합니다.

Node가 external Subflow result를 받을 때는 다음 binding을 사용합니다.

json
{
  "kind": "subflowResult",
  "subflowKey": "section_properties",
  "portKey": "section"
}

flowInput, nodeResultsubflowResult는 Flow 내부 source binding의 closed union입니다.

Group

json
{
  "groupKey": "member_check",
  "label": "Member check",
  "members": ["load_effect", "resistance_check"]
}

Group은 같은 Flow의 existing Node membership입니다. 실행 target, port, binding, revision과 result를 만들지 않습니다.

External Subflow

Imported Flow는 top-level externalSubflows[]에 regular Node와 분리해 저장합니다.

json
{
  "subflowKey": "section_properties",
  "target": {
    "kind": "flow",
    "scope": {
      "kind": "dependency",
      "packageKey": "section_flows",
      "packageVersion": "1.4.0",
      "packageDigest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
    },
    "flowKey": "section_properties",
    "revisionRef": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
    "semanticDigest": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
  },
  "inputBindings": {
    "section": {
      "kind": "nodeResult",
      "nodeKey": "section_input",
      "portKey": "section"
    }
  }
}

subflowKey는 caller Flow 안의 placement identity이고 target은 imported Flow의 exact immutable identity입니다. Caller는 이 target을 read-only nested Canvas로 열며 source Flow 편집은 owning scope에서 수행합니다. External Subflow input은 이 entry의 inputBindings가 소유하고, result는 subflowResult binding 또는 public Flow result source로 참조합니다.

Presentation

json
{
  "presentation": {
    "nodes": {
      "resistance_check": {"x": 320, "y": 180}
    }
  }
}

Canvas 좌표, viewport와 collapsed state는 실행 digest에서 제외합니다. Node setting과 binding은 presentation이 아닙니다.

SDK-linked와 PXFLOW-native

modeauthoring ownerSave
sdkLinkedPython source와 dependency locksource-safe diff 적용 뒤 rematerialize
pxflowNative.pxflow direct objecttyped Flow command로 새 revision 생성
readOnlypinned package/revision변경 불가; 명시적 copy 가능

SDK-linked Flow의 Canvas projection만 바꾸고 source를 그대로 두지 않습니다. 사용자가 검토한 candidate Flow와 source-safe edit 뒤 다시 materialize한 Flow의 semantic digest가 일치해야 합니다.

Authoring provenance

Report 또는 Flow revision이 AI가 제안한 command나 source-safe code action으로 만들어졌다면 immutable revision envelope와 audit ledger에 다음 provenance를 기록합니다. 이 metadata는 .pxreport 또는 .pxflow semantic body에 넣어 실행 digest를 바꾸지 않습니다.

json
{
  "origin": "aiAssisted",
  "actorPrincipalRef": "principal_...",
  "assistant": {
    "providerKey": "provider_key",
    "modelKey": "model_key",
    "toolContractVersion": 1,
    "structuredRequestDigest": "sha256:structured-tool-request-digest"
  },
  "planRef": "plan_...",
  "planDigest": "sha256:plan-digest",
  "commandId": "client-generated-idempotency-key",
  "approvalRef": "approval_...",
  "approvedByPrincipalRef": "principal_...",
  "approvedAt": "2026-08-02T00:00:00Z",
  "base": {
    "revisionRef": "sha256:base-revision-digest",
    "generation": 17,
    "sourceDigest": "sha256:base-source-digest"
  },
  "changeDigest": "sha256:ordered-command-or-source-diff-digest",
  "result": {
    "revisionRef": "sha256:new-revision-digest",
    "generation": 18,
    "sourceDigest": "sha256:new-source-digest"
  }
}
  • actorPrincipalRef는 변경 권한을 행사한 principal이고 approvedByPrincipalRef는 reviewed Apply를 승인한 principal입니다. AI assistant는 권한 principal을 대신하지 않습니다.
  • 변경 정책이 명시적 승인을 요구할 때 approvalRef로 승인 기록을 고정하며, 해당 정책이 적용되지 않는 변경에서는 이 field를 생략합니다.
  • assistantorigin = "aiAssisted"일 때 필수입니다. structuredRequestDigest는 schema-valid Tool request의 canonical digest이며 raw prompt, 문서 본문, secret과 전체 source를 저장하지 않습니다.
  • base.sourceDigestresult.sourceDigest는 SDK-linked source edit일 때 필수이고 direct .pxreport/PXFLOW-native edit에서는 생략합니다.
  • changeDigest는 plan의 normalized ordered command 또는 승인한 source diff를 고정합니다.
  • human-only Studio edit나 SDK script edit도 같은 envelope를 사용할 수 있으며 이때 originstudio 또는 sdk로 기록하고 assistant를 생략합니다.

AppSurfaceDescriptor

AppSurfaceDescriptor는 standalone 사용자 화면에만 사용합니다.

json
{
  "$schema": "https://schemas.pipelinexlab.com/app-surface-descriptor/v1.json",
  "descriptorVersion": 1,
  "adapterCategory": "app_surface",
  "categoryContractVersion": 1,
  "surfaceKey": "bearing_check",
  "formatKey": "streamlit",
  "formatContractVersion": 1,
  "title": "Bearing check",
  "description": "Interactive bearing pressure check.",
  "source": {
    "kind": "package",
    "packageKey": "structural_checks",
    "packageVersion": "2.3.0"
  },
  "entry": {
    "kind": "pythonModule",
    "module": "structural_checks.apps.bearing"
  },
  "targets": [
    {
      "kind": "flow",
      "scope": {
        "kind": "project",
        "workspaceKey": "structural_team",
        "projectKey": "bridge_package"
      },
      "flowKey": "bearing_review",
      "revisionRef": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
    }
  ],
  "hostCapabilities": [],
  "descriptorDigest": "sha256:e18a9b12ce83528cd4be048b3203e3eb4bfc97eec8b9115be4d2c2ace6caedfa"
}

지원 예는 pxflow_appstreamlit입니다. contract-only Engineering Paper editor는 AppSurfaceDescriptor가 아니며 public third-party V2 package UI로도 해석하지 않습니다. V2 Canvas는 host-owned component.declarative@2만 사용하고, rich surface는 RFC-016의 component-rich-surface-declaration@1이 소유합니다.

위 14개 field 순서가 V1 reviewable JSON의 canonical field order입니다. digest preimage는 descriptorDigest를 제외한 앞의 13개 field exact projection에 RFC 8785 JCS를 적용한 bytes이며, SHA-256 결과를 lowercase sha256: reference로 저장합니다. $schema는 preimage에 포함됩니다. V1은 exact Flow target 하나와 빈 hostCapabilities만 허용합니다. App Surface lifecycle, authorization, timeout과 resource-budget 계약이 승인되기 전에는 non-empty capability나 다중 target을 해석하지 않고 거부합니다.

Signed SDK package release envelope

json
{
  "schemaVersion": 1,
  "packageKey": "structural_checks",
  "packageVersion": "2.3.0",
  "platform": "python",
  "artifactDigest": "sha256:...",
  "descriptorDigest": "sha256:...",
  "dependencyLockDigest": "sha256:...",
  "functions": [],
  "components": [],
  "optionalContributions": [],
  "signature": {
    "keyId": "publisher-key",
    "algorithm": "ed25519",
    "value": "..."
  }
}

Release envelope는 public Python symbols, generated stubs, Function/Component descriptors, declarative presentation metadata, dependency lock와 code bytes를 함께 고정합니다. Function dependency의 current lock은 package key, exact version과 digest를 기록합니다. Component dependency lock과 admission은 구현된 exact ProjectScope data-path isolation에 Project authorization과 execution-trust closure를 더한 successor workflow이며, 현재 production third-party install을 허용하지 않습니다. Release envelope의 signature algorithm은 ed25519이며 publisher key는 제품이 관리하는 trust store에서 resolve합니다. 이 release envelope는 아래 container detached signature와 별도 artifact이지만 동일한 algorithm과 publisher-key authority를 사용합니다.

generic Component 또는 successor V2 package를 resolve할 수 없을 때 original package bytes는 package artifact owner가 실제로 보존한 경우에만 exact bytes를 recovery evidence로 사용하며 Flow 저장값에서 추론하지 않습니다. current reader는 exact 7-field ComponentRef, raw settings와 usage-derived partial ports만 보존하고 read-only unsupported로 표시합니다. generic recovery는 full/last-verified port snapshot을 새로 합성하거나 권위로 사용하지 않습니다. current V1 Engineering Paper의 required legacy contractSnapshot + document는 raw settings 안에서 기존 schema/Rust wire 그대로 읽고 저장하지만 generic Component recovery나 successor V2 admission 권위가 아닙니다. Component dependency required · package/versionAdd required package는 위 두 closure 이후의 successor action이며 현재 production install action이 아닙니다. 어느 경로도 비슷한 Component로 자동 치환하지 않습니다.

Project portable container

.pxproject는 안전한 ZIP container이며 manifest가 모든 content entry의 exact path, expanded bytes, digest를 선언합니다. root/dependency 의미는 projectSnapshot과 각 content schema가 소유하며 manifest entry에 kind, role, media type을 덧붙이지 않습니다.

json
{
  "schemaVersion": "1.0.0",
  "packageKind": "project",
  "publisher": "pipelinexlab",
  "packageKey": "bridge_package",
  "packageVersion": "1.0.0",
  "requiredFeatures": [],
  "requiredContractVersion": 1,
  "projectSnapshot": {
    "snapshotRevisionRef": "sha256:...",
    "copiedDocuments": [{"path": "content/documents/girder_review.pxflow", "revisionRef": "sha256:..."}],
    "settingsSnapshot": {"path": "content/settings/project.json", "revisionRef": "sha256:..."},
    "dependencySnapshots": [{"path": "content/dependencies/functions.json", "revisionRef": "sha256:..."}],
    "controlPlaneReferences": [
      {"kind": "history", "targetRef": "history:girder_review", "revisionRef": "sha256:..."},
      {"kind": "comment", "targetRef": "comment-thread:design-review", "revisionRef": "sha256:..."},
      {"kind": "output-revision", "targetRef": "output:girder_review_report", "revisionRef": "sha256:..."},
      {"kind": "execution-record", "targetRef": "run:girder_review", "revisionRef": "sha256:..."}
    ]
  },
  "entries": []
}

공용 PackageManifest wire는 위 9개 Project member 외에 optional capabilitiespythonDependencies를 허용합니다. 두 member는 생략과 명시적 []를 모두 빈 목록으로 decode하며, canonical writer는 빈 목록일 때 member를 생략합니다. projectSnapshot은 Project에서 필수이고 Flow·Report·Component에서는 금지합니다.

  • path traversal, duplicate normalized path, symlink와 digest mismatch를 거부합니다.
  • 문서·Project 설정·exact dependency snapshot은 content entry로 copy합니다.
  • 이력·댓글·출력 revision·실행 기록은 ZIP content가 아니라 revision-pinned control-plane reference로 유지합니다.
  • SDK-linked editing을 보존할 때 source와 lock을 함께 포함합니다.
  • secret, login session과 host permission handle을 포함하지 않습니다.
  • selected Result와 Component artifact는 immutable reference와 provenance를 유지합니다.

Portable package container envelope

.pxreport, .pxproject와 optional Component의 .pxcomponent는 같은 ZIP-compatible container 규칙을 공유합니다. .pxflow는 container가 아니라 단일 JSON 문서이므로 이 절의 대상이 아닙니다. archive entry 순서와 compression metadata는 물리 배치이며 logical identity로 사용하지 않습니다.

Component container manifest는 공용 member set을 그대로 사용하며 packageKind: "component"application/vnd.pipelinexlab.component+zip mimetype을 교차 검사합니다. detached signature는 optional이며 RFC-005 verification을 통과한 channel에서만 manifest/content closure를 verified라고 부릅니다. Component에는 Project 전용 projectSnapshot을 넣지 않습니다. Engineering Paper의 exact content role과 ComponentRef는 Engineering Paper Extension SDK가 소유합니다.

reserved envelope entry는 다음 셋입니다.

text
mimetype · manifest · detachedSignature
entry kind역할content closure
mimetypecontainer 종류를 빠르게 식별하는 물리 표식이며 schema version을 대신하지 않습니다excluded
manifest모든 content entry의 exact path, expanded bytes와 digest를 선언합니다excluded
contentmanifest가 선언한 root artifact, dependency, SDK source, asset, evidence와 selected snapshot입니다included
detachedSignature정책이 승인한 경우에만 존재하는 optional detached signature입니다excluded

manifest entry closed wire는 다음 세 member입니다.

text
path · bytes · digest

bytes는 unit 없는 size가 아니라 expanded byte count입니다. entry별 role·media type·kind는 이 wire에 없으며 content schema, projectSnapshot 또는 exact path contract에서 해석합니다.

content entry는 자신을 포함하는 manifest를 hash하지 않고 detached signature entry도 content closure에 넣지 않습니다. JSON entry의 digest preimage는 RFC 8785 JCS bytes이고 PDF·image·binary entry는 정확한 uncompressed bytes입니다. package-level byteDigest, semanticDigest, executionDigest는 모두 canonical manifest core와 path 오름차순의 ordered content-entry closure (path, expanded size, entry digest)를 공통 preimage로 사용합니다. byteDigest는 ordered exact uncompressed entry bytes를, semanticDigest는 ordered semantic projection과 binary exact bytes를, executionDigest는 ordered explicit execution projection과 resolved required-feature set을 추가합니다. 각 projection은 length-prefix로 field를 분리하고 SHA-256을 사용합니다. archive entry order와 compression metadata는 package identity에 들어가지 않습니다. container formatVersion, entry schemaVersion, Function/Component version과 runtime version은 하나의 version 문자열로 합치지 않습니다.

.pxreport container의 exact manifest member와 .pxproject의 consistent snapshot 범위는 승인된 공용 envelope·safe-reader 범위 밖의 후속 결정입니다. 위 .pxproject 예시는 현재 공용 manifest member와 소유권 경계를 보여 주지만 ProjectSnapshotRef, artifact copy-versus-reference를 새로 승인하지 않습니다.

V1 package content store는 exact-byte SHA-256 주소를 쓰는 whole-object store입니다. chunking과 automatic GC는 제공하지 않으며 relational reachability가 read 가능성을 결정하고 reader는 read 때 주소 digest를 다시 계산합니다. CAS 자체는 at-rest encryption, key id, rotation과 key custody를 구현하지 않고 ciphertext 의미를 package digest에 암묵적으로 넣지 않습니다.

현재 Component V1 선언 entry

packageKind: "component" container의 content closure는 정확히 하나의 content/component.json과 하나 이상의 manifested content/**/*.py entry로 구성합니다. 선언의 executorEntry는 그 Python entry 중 하나를 exact하게 가리켜야 합니다. helper module은 여러 개 허용하지만 V1 parser/internal installer fixture는 Python이 아닌 다른 content entry를 path 이름으로 거부합니다.

선언은 다음 member를 가진 closed JSON object입니다. 모르는 member는 거부합니다.

memberV1 규칙
componentKeysemantic key. 배치된 Node target의 componentKey와 같아야 합니다
componentContractVersion1 이상의 독립 정수. JSON schema version에서 추론하지 않습니다
label·description비어 있지 않은 문자열
classificationV1 값은 flow-node 하나입니다
inputs·resultsportKey, type, description을 가진 port 목록. type은 RFC-003 TypeDescriptor로 판정하고 input/result 전체에서 key를 중복할 수 없습니다
executorEntry같은 container가 나르는 content/ 아래 .py entry
presentationfuture host bridge를 위한 선언형 Canvas metadata. icon과 optional summaryResultKeys를 가지며 현재 production reader는 그리지 않습니다

manifest의 capabilities는 V1 parser/internal installer fixture에서 빈 목록만 통과합니다. 이는 production admission이 아닙니다. executorEntry는 동기 execute(inputs: dict, settings: dict) -> dict shape를 제공하지만, production third-party execution은 구현된 exact ProjectScope data-path isolation에 Project authorization과 execution-trust closure가 더해질 때까지 차단합니다.

한 portable Component container에 Component 선언을 여러 개 넣는 형식은 현재 V1이 아닙니다. 미래 V2는 manifest의 requiredContractVersion: 2로 명시적 dispatch하고 content/components/{componentKey}.json을 사용하는 contract_only successor입니다. 현재 reader·installer는 V2 container를 받지 않으며 V1 container를 자동 변환하지 않습니다.

안전한 package reader

package를 추출하거나 문서를 신뢰하기 전에 reader는 다음 조건을 거부합니다.

text
path-traversal · absolute-path · nul-byte-in-path · excessive-path-depth ·
duplicate-normalized-path · symlink-or-hardlink-entry · encrypted-or-unsupported-entry ·
oversized-entry · compression-bomb · unsupported-required-feature ·
unmanifested-content-entry · missing-manifested-entry · entry-digest-mismatch
  • open과 import는 검증·조회 동작입니다. 포함된 Python, Flow, HTML, macro, plugin과 AI를 여는 동작만으로 실행하지 않습니다.
  • 지원하지 않는 schemaVersionPX_SCHEMA_UNSUPPORTED_VERSION 진단과 read-only recovery로 거부합니다. V1에는 이전 production 버전이 없으므로 migration 대상도 없습니다.
  • manifest와 entry의 unknown core field는 PX_SCHEMA_UNKNOWN_FIELD로 거부합니다. unknown optional entry는 보존하며 저장할 때 조용히 삭제하지 않습니다. unknown required feature는 실행·저장을 거부하고 read-only safe open으로 낮춥니다.
  • 승인된 상한을 넘는 entry와 compression bomb은 PX_RESOURCE_LIMIT로 거부합니다.
  • signature를 검증했더라도 현재 권한, Component trust와 실행 sandbox 검증을 생략하지 않으며 권한이 없으면 PX_PERMISSION_DENIED로 거부합니다.
  • reader는 거부 사유를 안정적인 진단으로 보고하며 host absolute path와 archive 내부 bytes를 진단에 포함하지 않습니다.

승인된 safe-reader limit은 다음과 같습니다.

limitvalueunit
entryCountMax2000entries
compressedTotalMax200MB
expandedTotalMax512MB
singleEntryMax100MB
compressionRatioMax100to-one
pathDepthMax20path-segments

compressedTotalMax 또는 expandedTotalMax를 넘으면 compression-bomb으로 거부합니다. 이 V1 reason은 compression ratio attack과 단순 total-size overrun을 구분하지 않습니다.

이 값은 ATTACK-DEFENSE limit이며 명시적으로 V1 capacity promise가 아닙니다. 이 limit이 serve하는 product size tier는 한 팀이 authoring한 단일 Report 또는 Project package이고 bulk archive가 아닙니다. Product UI나 API는 이 공격 방어 상한을 업로드·저장·처리 용량 약속으로 표현하지 않습니다.

package bytes에는 다음을 넣지 않습니다.

text
auth-session-or-publish-token · password-api-key-or-unredacted-environment-secret ·
host-absolute-path · other-user-workspace-path ·
version-control-dependency-or-build-cache · server-log-queue-worker-or-deployment-state ·
browser-storage-dump-recent-file-list-or-ui-preference ·
unapplied-ai-prompt-session-or-hidden-tool-trace ·
tenant-workspace-custody-membership-or-access-grant · invite-or-link-secret ·
collaboration-session-or-presence · publication-delivery-ticket ·
local-permission-handle · mutable-latest-reference

이 열네 class는 관찰 가능성에 따라 닫힌 두 집합으로 나뉩니다. safe reader는 archive와 manifest entry path 양쪽에서 host-absolute-path, other-user-workspace-path, version-control-dependency-or-build-cache, browser-storage-dump-recent-file-list-or-ui-preference 네 class를 class 이름으로 거부합니다. 나머지 열 class는 signer가 서명 전에 배제해야 하는 publisher-signature obligation입니다. reader는 그 열 class를 위해 byte heuristic을 만들지 않습니다. 따라서 safe-reader 성공은 envelope metadata와 네 entry-path rule이 유효하다는 뜻이며, 나머지 열 class가 content bytes에 없다는 판정은 아닙니다.

Dependency closure completeness

package의 authored dependency selector와 commit 시점의 exact resolved closure를 구분합니다. Run, Review와 배포는 exact resolved closure만 고정하며 과거 closure를 최신 정책으로 다시 해석하지 않습니다. Import는 새 scope와 grant를 만들고 허용된 provenance만 lineage로 남깁니다.

portability status의미
portable아래 완전성 조건을 모두 만족합니다
external-dependenciespackage 밖에서 다시 확인해야 하는 dependency가 남아 있습니다
incompleteclosure 검증이 통과하지 못했습니다

portable은 다음을 모두 만족할 때만 표시합니다.

text
root-document-and-metadata-hash-verified ·
embedded-logic-dependency-owner-and-revision-verified ·
required-function-and-component-version-resolved ·
required-local-asset-and-evidence-included · pinned-result-snapshot-closure-complete ·
no-unresolved-cross-report-value-source · host-runtime-compatibility-declared

package 밖에 남을 수 있는 dependency 사유는 다음과 같습니다.

text
private-organization-function · non-redistributable-component · large-remote-dataset ·
external-evidence-service · host-runtime

Save와 Save As preflight는 남은 external dependency의 개수와 사유를 표시합니다. 누락을 숨기거나 package가 완전 self-contained라고 표현하지 않습니다. closure 밖 target을 portable package에 담으려 하면 PX_TARGET_NOT_PORTABLE 진단과 dependency 첨부 action을 제공합니다.

Package signature envelope

detached signature는 canonical manifest core와 정렬된 content-entry closure를 서명합니다.

text
covered: canonical-manifest-core · ordered-content-entry-closure · content-entry-path ·
content-entry-size · content-entry-digest
text
not-covered: manifest-entry-self-digest · detached-signature-entry · mimetype-entry-bytes ·
archive-entry-order · compression-metadata

ZIP compression allowlist는 storeddeflated뿐입니다. bzip2를 포함한 제3 방식은 encrypted-or-unsupported-entry로 거부합니다.

두 envelope의 algorithm과 key authority는 다음과 같습니다.

signature envelopealgorithmpublisher key authority
container-detached-signatureed25519product-managed-trust-store
publisher-release-envelopeed25519product-managed-trust-store

Container detached signature와 publisher release envelope는 서로 다른 envelope입니다. 전자는 위 canonical manifest core와 content-entry closure를 서명하고, 후자는 package code와 descriptor를 고정하는 publisher artifact입니다. 한 envelope의 valid signature를 다른 envelope의 signature로 재사용하지 않습니다.

Key revocation은 다음 rule을 따릅니다.

revocation rulevalue
mechanismsigned-revocation-list
validity-windowrequired
stale-or-unavailable-listreject-verification
revoked-after-signingreject-on-verification-after-revocation
trusted-timestamp-exceptionnone-in-v1

Verifier는 verification time을 포함하는 validity window의 product-signed revocation list를 사용합니다. List가 없거나 아직 유효하지 않거나 만료됐으면 fail closed로 거부합니다. Key가 package 서명 뒤에 revoke됐더라도 revoke 이후의 open/import/reverification에서는 그 package를 거부합니다. V1 envelope에는 independent trusted timestamp가 없으므로 claimed signing time으로 revocation을 우회하거나 과거 trust 결과를 grandfather하지 않습니다. Trust store는 pipelineXlab_pxflow/ops/trust/publisher-trust-store.v1.json, revocation list는 pipelineXlab_pxflow/ops/trust/publisher-revocation-list.v1.json에서 ship합니다. 두 파일은 RFC 8785 canonical JSON이며 list signature input은 signatureValue를 제외한 object입니다. Window 경계는 inclusive이고 최대 길이는 7,776,000초(90일)입니다.

Run과 ResultSnapshot

Run record는 portable Flow source가 아니라 Runtime ledger가 소유합니다.

json
{
  "runRef": "run_...",
  "flowRevisionRef": "sha256:...",
  "inputDigest": "sha256:...",
  "state": "succeeded",
  "resultSnapshotRef": "snapshot_..."
}
json
{
  "snapshotRef": "snapshot_...",
  "runRef": "run_...",
  "results": {
    "utilization": {
      "type": {"kind": "float64"},
      "unit": "1",
      "value": 0.83
    }
  },
  "provenance": {}
}

큰 Table, image, model과 binary artifact는 inline bytes 대신 content-addressed resource reference를 사용할 수 있습니다. resource read는 현재 permission과 retention을 다시 확인합니다.

optional Engineering Paper Component artifact

저장 수명주기는 다음으로 고정합니다.

Engineering Paper artifact ref는 UUID나 opaque handle이 아니라 artifactKey · contentDigest입니다. Document artifact는 version 1 Document와 다음 provenance 네 member를 함께 고정합니다.

  • sourceFlowRevision: exact saved Flow revision digest
  • nodeKey: producing Engineering Paper Node의 semantic key
  • evaluatorRelease: publisher를 포함한 exact 7-field ComponentRef
  • inputDigest: ordinary Flow binding이 resolve한 input의 digest

paper.results.document는 이 typed ref 하나만 반환하고 Document payload를 복제하지 않습니다. PDF artifact DTO는 schemaVersion · artifactRef · sourceDocumentRef · exportProfileDigest를 고정하며 PDF bytes는 storage operation에만 전달합니다. local profile은 SQLite metadata와 공통 FilesystemCas를 사용하고 object reachability가 유지되는 동안 bytes를 보존합니다. 읽기는 active exact-project AccessGrant와 artifact.read를 다시 확인하고, V1은 implicit expiry, delete 또는 purge operation을 추가하지 않습니다. artifact bytes를 편집해서 Flow source, Node draft 또는 독립 .pxreport를 변경하지 않으며 Report PDF repository도 재사용하지 않습니다.

Decode failure recovery와 legacy 분류

Native recovery는 exact original bytes를 보존한 read-only open만 제공하며 WASM과 Python binding에는 recovery entry point가 없습니다. Legacy manifest는 top-level manifest member가 있는 JSON object, yaml은 non-JSON UTF-8에서 첫 nonblank line이 digits-only schemaVersion:인 경우뿐입니다. 그 밖의 입력은 legacy가 아닙니다. V1이 실제 생산하는 detectedFormatmanifestyaml 두 값뿐이고 unknown은 생산 vocabulary에서 제외됩니다.

Migration과 unknown data

  • schema migration은 원본을 덮어쓰기 전에 새 revision/package로 작성합니다.
  • unknown target kind, binding kind와 Component-owned document schema는 fail closed로 처리합니다.
  • generic/successor V2에서 resolve할 수 없는 Component Node는 exact 7-field ComponentRef, raw JSON settings와 usage-derived partial ports를 보존하되 편집·실행하지 않고 read-only unsupported로 엽니다. original package bytes는 package artifact owner가 실제로 보존한 경우에만 exact bytes로 복구합니다. current V1 Engineering Paper legacy setting은 이 generic 범위에서 제외하고 기존 wire로 호환 보존합니다.
  • migration preview는 바뀌는 key, contract, dependency와 artifact 영향 범위를 보여 줍니다.

구현 불변식

  1. Report revision은 document structure와 flowConnections를, Flow revision은 Node, binding, Group과 Node-owned setting을 소유합니다.
  2. Project source 또는 package가 소유한 Function과 package가 소유한 Component는 explicit stable key를 사용합니다. 한 Python distribution은 Component를 여러 개 export할 수 있지만 portable V1 container 하나에는 declaration 하나만 있습니다. generic Component와 successor V2 Node는 saved exact 7-field ComponentRef, raw settings와 input binding·public result usage에서 유도한 partial ports만 저장하며 full/last-verified contract snapshot을 새로 합성하지 않습니다. current V1 Engineering Paper의 legacy contractSnapshot은 domain-specific raw settings의 일부로 기존 reader/writer가 보존하되 generic recovery authority로 승격하지 않습니다. original package bytes는 package artifact owner가 실제로 보존한 경우에만 별도 authority로 사용합니다.
  3. .pxreport.flowConnections는 exact Flow revision의 public port만 참조하고 Flow 내부 binding을 복제하지 않습니다.
  4. Engineering Paper의 optional Component와 editor block/Node setting 결합은 engineering-paper-profile.v1.jsoncontract_only 범위이며 현재 기능으로 해석하지 않습니다.
  5. 모든 실행은 saved Flow revision의 일반 Node binding을 사용합니다.
  6. App Surface와 Component Node editor를 같은 descriptor로 저장하지 않습니다.
  7. ResultSnapshot과 artifact는 생성 뒤 수정하지 않습니다.

이어서 보기

Flow direct document V2: result viewer binding

V2 schema는 pxflow-direct.v2.schema.json입니다. 기존 V1의 계산 node·port·dependency 정의와 top-level 순서를 유지하고 schemaVersion: 2에서 presentation.viewers를 required로 추가합니다. 빈 map도 허용합니다. V1에서는 이 멤버를 거절합니다. V1 decode/encode는 원래 bytes와 digest를 유지하며 V2로 자동 변환하지 않습니다.

viewers는 semantic viewer key를 key로 하고 다음 닫힌 값을 저장하는 map입니다.

json
{
  "renderer": {"key": "core.result", "version": 1},
  "results": ["geometry", "mesh", "centroids"],
  "position": {"x": 640, "y": 0}
}

results는 같은 문서에 선언한 공개 result key의 비어 있지 않은 ordered 배열입니다. 중복과 존재하지 않는 key는 Core에서 거절합니다. 목록과 viewer 수는 기존 collection 상한 100000을 따릅니다. 좌표는 기존 nodePosition과 동일한 유한한 숫자입니다. viewer map은 key순으로 canonicalize하며 결과 선택 순서는 유지합니다.

뷰어는 계산 DAG 밖의 presentation이며 Node·연결·결과의 실행 의미를 바꾸지 않습니다. 문서 bytes와 semantic digest에는 포함되므로 편집 시 새 문서 revision이 됩니다. 기존 계산 cache의 identity를 재해석하거나 표시 변경만으로 새 Run을 제출하지 않습니다.

Run 선택은 reader/session 상태입니다. URL·base64·host 경로·Run의 mutable latest pointer를 저장하지 않습니다. core.result@1은 host renderer identity이며 외부 JavaScript 실행 권한이 아닙니다. SDK 선언은 flow.viewer를 사용합니다.

현재 Core는 Flow 버전 1과 2를 읽습니다. 미래 문서 버전 또는 지원하지 않는 core.result renderer version은 PX_SCHEMA_UNSUPPORTED_VERSION과 원본 bytes 보존으로 거절합니다. 새 reader의 복구 fixture는 미래 버전 3을 사용합니다. 기존 V1 복구 fixture는 당시 reader 계약의 이력으로 유지합니다.