본문으로 건너뛰기

Component container V2와 서드파티 Node UI

Python Component package는 이미 하나 이상의 Component를 export하는 배포 단위입니다. 이 정책은 그 용어를 바꾸지 않고, 현재 선언 하나만 담는 Component container V1을 미래에 선언 여러 개를 담는 V2로 확장합니다. Canvas에는 검증된 선언만 투영합니다.

상태와 권위

현재 V1 wire format은 이 V2 정책으로 바꾸지 않습니다. 이 정책의 상태는 availability: implemented-with-named-remaining-slices입니다. 아래 V2 형식의 reader, installer와 qualified resolver는 bounded local/admin Project lane에 구현됐습니다 (v2ReaderImplemented·v2InstallerImplemented·v2MultiComponentResolverImplemented: true). decorator에서 container를 만드는 authoring compiler도 구현됐습니다 (v2AuthoringCompilerImplemented: true). current portable reader는 V1과 V2 container를 모두 읽지만, production third-party install admission은 아래 trust·authorization closure가 닫힐 때까지 차단합니다. 현재 V1 Component data path는 exact Project 범위로 격리되고 저장된 trust evidence를 fail closed로 다시 판단합니다. 다만 요청 Project를 인증 principal의 권한에 결속하지 않고, install 시 cryptographic verification·current revocation 확인과 실행 시 acceptance channel·warning·audit 재검증도 없습니다. 이 경계와 production admission gate가 닫히기 전에는 current V1을 포함한 모든 서드파티 Component 활성화를 차단합니다.

2026-09-01 correction; previous wording kept verbatim: "아래 V2 형식은 결정과 검증 도구까지 정의됐지만 reader, installer, resolver에는 아직 구현되지 않았습니다. current portable reader가 이해하는 형식은 V1뿐이지만". node-presentation-profile.v2.jsonimplementationv2ReaderImplemented, v2InstallerImplementedv2MultiComponentResolverImplemented를 모두 true로 고정하고, crates/pxflow-runtime/src/package_install.rscontent/components/{componentKey}.json entry를 읽습니다. 이 문서 자신도 아래에서 qualified resolver를 v2MultiComponentResolverImplemented: true로 인용하므로, 이 문단은 같은 문서의 다른 문단과 모순된 상태였습니다. contract_only availability와 production third-party admission 차단은 바뀌지 않았습니다.

2026-09-01 correction; previous wording kept verbatim: "이 정책의 상태는 availability: contract_only입니다", "decorator에서 container를 만드는 authoring compiler는 아직 없습니다 (v2AuthoringCompilerImplemented: false)". 두 진술 모두 코드보다 뒤처져 있었습니다. sdk/python/src/pipelinexlab/_packager.pywrite_declarations가 authoring compiler이고, reader·installer·resolver·Project-scoped catalog·host-owned declarative renderer·rich editor surface·Python worker execution 경로가 모두 구현·검증돼 있습니다. 구현되지 않은 것은 implementation 아래에 항목별로 이름이 있고, pipelineXlab_pxflow/scripts/check-contract-implementation-flags.mjs가 그 boolean 하나하나를 코드의 named symbol에 대조합니다. production third-party admission 차단은 바뀌지 않았습니다.

2026-09-01 correction; previous wording kept verbatim: "host-owned component.declarative@1", "이 renderer는 현재 production catalog에 등록되지 않았고 bridge도 구현되지 않았습니다", "component.declarative@1은 296×408px 고정 border box", "첫 component.declarative@1은 이 key와 declared type·unit label만 그립니다", "해당 entry, isolation, messaging과 실행 자원 계약은 전부 RFC-014가 소유합니다". renderer는 contract version 2로 갔고 production catalog에 등록돼 있습니다. 하나였던 box는 선언한 port 수가 고르는 세 registered tier가 됐고, summary key는 현재 Run의 result snapshot에서 host가 읽어 선언된 type·unit으로 그립니다. rich surface의 entry와 message는 RFC-016의 component-rich-surface-declaration@1이 소유합니다. 값의 loading, error, stale state rendering, Canvas에 mount하는 rich surface와 production third-party admission 차단은 바뀌지 않았습니다.

권위는 다음처럼 나눕니다.

이 profile은 RFC-014 이전에 고정한 제한된 Flow Node slice의 규범적 contract-only 부분 authority입니다. 여기서는 V2 declaration path/index, exact ComponentRef join, host-declarative presentation, category·typed port admission, 현재 deny-all capability와 빈 Python dependency admission 상속, Components discovery catalog·favorites·catalog performance, RFC-005 trust 소비와 Canvas performance gate를 소유합니다. RFC-014는 generic plugin manifest, runtime ABI/WIT, capability vocabulary·grant·enforcement, 실행 자원 budget, rich-surface entry/message, publisher trust-root rotation과 plugin rollback을 소유합니다. RFC-014가 이 slice를 소비하지 않고 바꾸려면 새 contract version을 발행해야 하며, RFC-014 승인 전 V2를 활성화하지 않습니다. 다만 manifested package-local Python helper의 import/module root와 worker materialization은 rich-surface entry/message가 아니며 별도 versioned runtime Python module ABI·worker contract가 소유합니다. RFC-014가 이 별도 계약을 대신하거나 helper의 exact bytes 검증을 완화하지 않습니다.

결정 요약

text
Python Component package / distribution        현재도 Component 1..N export
             │ future ComponentDescriptor v2 compile

Component container V2                         portable 설치·검증 단위
├─ content/components/model_check.json        Component 선언 1
├─ content/components/pressure_limit_check.json        Component 선언 2
├─ content/model_check.py                     worker 실행 entry
└─ content/pressure_limit_check.py                     worker 실행 entry
             │ install 때 한 번 검증·색인

exact ComponentRef N개                         검색·배치·실행 단위


host component.declarative@2                   Canvas shell·Handle·상태

채택 규칙은 네 가지입니다.

  1. Python package/distribution은 하나 이상의 Component를 export하는 배포 단위이고, Component는 검색·배치·실행 단위입니다. Component container는 portable install reader가 검증하는 artifact 단위입니다.
  2. V2 container는 manifest에 requiredContractVersion: 2를 요구합니다. V1 reader는 이를 이름이 있는 오류로 거부하므로 일부만 잘못 읽지 않습니다.
  3. Component마다 content/components/{componentKey}.json 선언을 하나 둡니다. entry의 raw bytes는 pxflow-core::jcs_bytes가 만든 RFC 8785 JCS bytes와 정확히 같아야 합니다. installer는 exact semantic-key JSON path만 한 번 색인하고 manifest entry와 decoded declaration의 bijection을 검증하며 Canvas render 중 container를 scan하지 않습니다.
  4. 외부 code는 Canvas React tree에 들어오지 않습니다. V2 surface bridge는 선언을 component.declarative@2 value로 투영합니다. 이 renderer는 production catalog에 등록돼 있고 bridge도 구현돼 있습니다.

component.declarative@2 value는 ComponentRef, label과 presentation만 소유합니다. typed port를 두 번째로 복제하지 않습니다. bridge가 admitted declaration의 inputs·results를 host Node의 공통 port view로 투영하고, 공통 shell이 Handle·type·unit을 그립니다. renderer value와 port view가 서로 다른 port 계약을 소유하지 않습니다. renderer-value v1의 label·presentation shape는 자기 schema 안에 닫혀 있으며 versioned authoring descriptor나 declaration schema를 $ref하지 않습니다. 따라서 authoring v3가 나와도 renderer-value v1의 허용 범위가 조용히 바뀌지 않습니다.

이 투영에서 FlowPortKind는 연결 가능성의 축약본이 아니라 색·선 모양을 고르는 손실 있는 시각 계열입니다. installer는 admitted declaration의 exact RFC-003 TypeDescriptor와 canonical unit만 resolution index에 유지합니다. package나 renderer value에 FlowPortKind를 저장하지 않고, host Flow surface가 immutable declaration에서 portKey와 시각 계열을 파생해 exact ComponentRef 단위로 memoize합니다. V2의 닫힌 매핑은 다음과 같습니다.

RFC-003 TypeDescriptor.kindhost FlowPortKind
booleanscalar
int64scalar
float64scalar
decimalscalar
stringscalar
enumscalar
datescalar
timestampscalar
durationscalar
recordrecord
listtable
tabletable
binaryfile
artifactfile
evidencereference
calculation_tracedocument

listtable이 같은 시각 계열이어도 연결 검사는 full RFC-003 TypeDescriptor와 unit으로만 판정합니다. FlowPortKind, Handle 색과 stroke pattern은 호환 판정을 할 수 없습니다. RFC-003에 새 canonical kind가 생기면 이 versioned 매핑을 먼저 갱신하기 전까지 V2 admission을 거부합니다. unresolved는 알려진 type의 fallback이 아니라, 현재 runtime projection이 빠졌거나 해석되지 않았을 때 neutral diagnostic을 그리는 host 전용 상태입니다. 이 규칙은 contract-only V2 activation 규칙이며 현재 V1 reader의 provisional projection을 구현 완료로 재해석하지 않습니다.

visible type label은 별도 bounded grammar host-type-label@1을 사용합니다. simple kind와 calculation_trace는 kind 그대로, record는 record<recordKey>, list/table은 각각 list<item-label>·table<row-label>, enum은 값 문자열을 나열하지 않고 enum[count]로 표시합니다. descriptor depth는 admission 상한 16을 그대로 따르고 enum value는 label, tooltip, ARIA accessible name과 검색 index 어디에도 복제하지 않습니다. full descriptor는 core validator의 호환 판정과 bounded detail view에만 유지합니다.

V2 content closure는 declaration JSON과 manifested content/**/*.py만 허용합니다. executorEntry는 그 Python entry 중 하나를 exact하게 가리켜야 하며, 여러 Component가 공유하는 같은 executor entry는 허용합니다. executor가 아닌 package-local helper Python entry도 허용하되 manifest에 들어 있고 exact bytes·digest 검증과 아래 수량/byte 상한을 모두 통과해야 합니다. unmanifested Python bytes는 계속 거부합니다. helper가 있는 release의 import/module root와 worker materialization은 별도 versioned runtime Python module ABI·worker contract가 승인되기 전에는 activation하지 않습니다. 이 owner는 RFC-014 rich-surface 계약과 분리합니다. JavaScript, HTML, CSS, binary와 다른 임의 content entry는 rich-surface field가 없어도 거부합니다.

V2에서는 decoded manifest의 pythonDependencies가 반드시 빈 목록이어야 합니다. wire에서는 Rust serializer의 canonical empty 표현을 보존해 member 생략과 명시적 []를 동등하게 받습니다. 현재 V1이 dependency range를 받는 동작은 바꾸지 않지만, numpy>=2 같은 range는 resolved wheel·transitive dependency·runtime byte를 exact ComponentRef에 고정하지 못합니다. versioned exact dependency lock, SBOM, artifact digest와 runtime materialization owner가 승인되기 전에는 V2에서 이를 재사용하지 않습니다. 이 후속 계약은 이 문서가 소유한 것으로 가장하지 않으며, dependency-free V2 activation을 막지는 않되 승인 없이 non-empty dependency admission을 열지 않습니다.

public marketplace release는 RFC-005의 product-managed trust store와 verification time을 포함하는 현재 signed revocation list를 사용합니다. list가 없거나 stale이면 거부하고, signing 뒤 revoke된 key도 다음 verification에서 거부하며 cached/grandfathered 성공은 없습니다. public marketplace는 verified만 admission하고 unsigned를 거부합니다. local user-accepted와 admin-attached는 unsigned를 Project installed closure에 넣을 수 있지만 각각 explicit acceptance와 audit가 필요하고, local unsigned에는 warning도 필요합니다. 서명 bytes가 있다면 present-unverified, invalid, revoked, stale/unavailable verification evidence는 어느 channel에서도 acceptance로 덮어쓸 수 없습니다. fixture는 admission과 execution 각각에서 세 channel과 여섯 signature state의 전체 18개 조합을 canonical case로 고정하고, acceptance·warning·audit 누락은 각 단계의 별도 negative case로 검증합니다. execution case는 verified signature에 fresh revocation evidence를, unsigned local/admin에는 명시적인 N/A를 요구합니다. verified local/admin도 해당 channel의 explicit acceptance와 audit를 거칩니다. 이 작은 matrix를 machine profile과 fixture가 함께 고정하며 signature 존재만으로 실행 권한을 주지 않습니다.

Project 격리와 실행 trust는 하나의 activation closure입니다

설치·catalog·배치·실행이 공유하는 범위는 공개 identity authority의 exact ProjectScope { kind: "project", workspaceKey, projectKey }입니다. projectKey만 비교하거나 ComponentRef에서 범위를 추측하지 않습니다. membership source가 Project install이면 요청의 ProjectScope와 installed closure의 세 member가 모두 exact-match해야 합니다. host-bundled이면 Project row를 지어내지 않고 product-scope의 admitted exact release membership과 host admission을 검사합니다. 두 source 모두 실행 시점의 RFC-005 trust evidence를 다시 판단합니다. fixture는 다른 Project뿐 아니라 **다른 Workspace의 같은 projectKey**도 거부하고, admitted host-bundled release가 Project row 없이 같은 effective closure에서 resolve·execute되는 경로도 고정합니다.

현재 V1 data-path 격리는 구현되어 있습니다. install_package의 Component branch는 받은 ProjectScopeinstall_component_package와 contribution row에 전달하고, local/Postgres primary key·catalog·detail query는 exact (workspaceKey, projectKey) 또는 host-owned product-scope row만 허용합니다. 기존 unscoped third-party row는 Project를 추측하지 않고 접근 불가능한 reserved scope로 이관합니다. execution request DTO는 아직 typed ProjectScope가 아닌 workspace_ref·project_ref 문자열 pair를 쓰지만 repository lookup에는 두 값을 모두 적용합니다. partial scope는 installed row를 반환하지 않습니다. 다만 요청 scope를 authenticated principal의 실제 Project 권한에 결속하는 authorization은 아직 없습니다. production Flow reader가 Component를 unsupported로 표시하는 현재 상태와 이 data-path 격리 완료는 별개입니다.

형식 호환성은 existingContainerBytesRemainReadable: true로 보존합니다. 이는 기존 V1 container의 bytes를 읽을 수 있다는 뜻이지, 현재 서드파티 설치 admission을 허용한다는 뜻이 아닙니다. 현재 installer는 executor bytes를 commit_reachable(executor_bytes)로 즉시 reachable하게 만든 뒤 RecordComponentContribution으로 scoped contribution row를 바로 기록하며, 별도 inactive quarantine 상태가 없습니다. 따라서 thirdPartyInstallAdmissionAllowedBeforeTrustAndAuthorizationClosure: falseinactiveQuarantineInstallStateImplemented: false를 함께 고정합니다. 후자는 현재 구현 감사 상태일 뿐 별도 admission gate가 아니며, inactive quarantine 구현만으로는 admission을 열 수 없습니다. data-path 격리는 닫혔지만 trust·authorization closure와 production gate가 모두 닫힐 때까지 third-party V1 install admission 자체를 호출 경계에서 거부해야 합니다. 이것은 현재 runtime이 production caller policy를 이미 강제한다는 주장이 아닙니다. machine profile은 productionThirdPartyV1InstallAdmissionGateImplemented: falseproductionThirdPartyV1ExecutionAdmissionGateImplemented: false를 고정합니다. 두 gate가 아직 없는 상태는 P0-before-third-party-install-admission blocker입니다.

현재 V1 trust path는 여전히 서드파티 실행 승인으로 간주할 수 없습니다. trust authority가 없으므로 install은 detached signature가 있는 Component를 contribution 기록 전에 거부합니다. 명시적으로 수용된 unsigned Component만 absent·not-checked로 저장합니다. catalog와 detail은 공용 strict predicate로 present-unverified, invalid, revoked, stale/unavailable evidence를 숨기고, product-scope row에는 registry-verified valid·valid-window만 허용합니다. worker도 저장된 signature와 revocation 상태를 실행 전에 다시 판단합니다. 그러나 acceptance channel·warning·audit는 row에 저장되지 않아 실행 시 재검증할 수 없고 current revocation source도 다시 조회하지 않습니다. 요청 acceptance가 authenticated principal의 authority에서 파생되지도 않습니다. 이 남은 경계는 P0-before-any-third-party-component-activation blocker입니다.

V2 activation 전에는 다음 한 closure를 함께 구현하고 conformance fixture로 증명해야 합니다.

  1. Project-installed Component는 exact ProjectScope를 install에서 저장소 row까지 손실 없이 전달하고, host-bundled Component는 host-owned product-scope provenance를 보존합니다.
  2. local/Postgres schema와 모든 catalog/detail/execution read는 product-scope admitted row 또는 exact request (workspaceKey, projectKey) row만 허용하는 닫힌 predicate를 사용합니다.
  3. execution materialization 때 exact 7-field ComponentRef와 request Project effective Component closure membership을 다시 검사합니다. host-bundled source는 product-scope membership·host admission과 current RFC-005 verification/revocation evidence를, Project-installed source는 exact request scope와 RFC-005 channel matrix를 다시 계산합니다. signed verified release만 실행 시점의 fresh revocation evidence를 요구합니다. unsigned local/admin release에는 revocation evidence가 없으므로 N/A로 두고 explicit acceptance·warning 정책·audit를 다시 검사합니다.
  4. cross-project와 cross-workspace-same-project fixture가 install → catalog → execution 전체에서 같은 이름의 실패로 거부되는지 검증합니다.

현재 V1 보존선

현재 portable V1 reader는 Component container마다 정확히 하나의 content/component.json을 받습니다. 이는 Python Component package/distribution이 여러 public Component symbol을 export할 수 있다는 현재 용어·authoring 계약을 좁히지 않습니다. container 선언은 독립적인 componentContractVersion, classification: "flow-node", typed inputs·results, executorEntry, 선언형 presentation.icon·presentation.summaryResultKeys를 가집니다. backend install, install 후 product resolver의 exact ComponentRef 조합, worker의 release/source 재검증은 구현되어 있습니다. 그러나 현재 production Flow reader는 이 선언을 component.signed@1로 연결하지 않고 Component target을 read-only unsupported diagnostic으로 표시합니다. design-system entry가 존재한다는 사실은 production bridge 구현 증거가 아닙니다.

V1의 authority는 서로 섞지 않습니다.

  • portable container declaration: container당 하나, classification: flow-node, icon·summaryResultKeys, 한 개 이상의 manifested Python entry와 그중 exact executor
  • Python distribution: 여러 Component symbol export 가능
  • authoring ComponentDescriptor: V2 선언과 같은 닫힌 primary task category(input, transform, calculate, review, deliver, control)와 그 아래 publisher가 정하는 열린 subcategory
  • .pxflow componentNode: exact target, input binding과 optional instance settings
  • current V1 Engineering Paper domain setting: raw settings 안의 required legacy contractSnapshot + document를 기존 schema/Rust reader가 계속 읽고 저장합니다. 이 snapshot은 generic Component recovery authority나 successor V2 setting 계약이 아닙니다.
  • production surface: design-system에 component.signed@1component-settings가 등록돼 있으나 reader bridge는 미구현. 현재 reader가 유도하는 port는 saved bindings/results usage의 partial projection이며 Component label이나 full verified port snapshot이 아닙니다.

V2 때문에 V1 container를 자동 rewrite하거나 기존 .pxflow target을 새 ref로 바꾸지 않습니다.

V2 선언

V2 선언은 V1의 실행·typed port·최소 presentation 의미를 보존하면서, 다중 선언을 안전하게 색인하는 데 필요한 schemaVersion과 task category를 명시합니다.

json
{
  "$schema": "https://pipelinexlab.com/contracts/component-package-declaration.v2.schema.json",
  "schemaVersion": 2,
  "componentKey": "model_check",
  "componentContractVersion": 3,
  "label": "Model check",
  "description": "Checks a connected structural model.",
  "classification": "flow-node",
  "category": "review",
  "subcategory": "model",
  "typeDefinitions": {
    "records": [
      {
        "recordKey": "structural_model",
        "fields": [
          {
            "fieldKey": "model_id",
            "type": { "kind": "string" },
            "required": true,
            "nullable": false
          }
        ],
        "rowKeyField": "model_id"
      }
    ]
  },
  "inputs": [
    {
      "portKey": "model",
      "type": { "kind": "record", "recordKey": "structural_model" },
      "description": "Structural model reference.",
      "required": true,
      "nullable": false
    }
  ],
  "results": [
    {
      "portKey": "issue_count",
      "type": { "kind": "int64" },
      "description": "Detected model issue count.",
      "nullable": false
    }
  ],
  "executorEntry": "content/model_check.py",
  "presentation": {
    "icon": "boxes",
    "summaryResultKeys": ["issue_count"]
  }
}

Future SDK/build는 같은 필드를 가진 ComponentDescriptor v2를 먼저 만들고, publish 때 $schema URI를 declaration schema URI로 바꾼 뒤 전체 declaration을 RFC 8785 JCS로 직렬화합니다. executorEntry까지 descriptor가 명시하므로 compiler가 경로를 추측하지 않습니다. fixture와 checker는 descriptor/declaration의 componentKey bijection과 schema URI를 제외한 deep equality를 검증합니다. 현재 SDK/build는 이 V2 compiler를 구현하지 않았으므로 두 JSON을 사람이 따로 편집하는 workflow는 허용하지 않습니다.

componentContractVersionschemaVersion에서 계산하지 않습니다. 전자는 배치된 Component의 의미·호환 version이고 후자는 선언 JSON reader version입니다. fixture가 schemaVersion: 2componentContractVersion: 3을 함께 사용해 이 차이를 검증합니다. V2 declaration, exact ComponentRef, Canvas projection과 Node target의 componentContractVersion은 모두 JavaScript safe integer 상한 9,007,199,254,740,991을 공유합니다. 현재 V1 public schema의 더 넓은 integer shape는 호환을 위해 그대로 두되, renderer-value schema는 V1 ComponentRefallOf로 소비하면서 이 상한을 로컬로 추가하고 V2 join과 materialization도 같은 bound를 강제해 browser에서 identity가 반올림되지 않게 합니다.

Component의 지속 identity는 (publisher, packageKey, componentKey)입니다. typeDefinitions, inputs, results 세 필드의 closed projection을 RFC 8785 JCS로 직렬화하고 SHA-256한 값이 typed compatibility digest입니다. 같은 identity와 같은 componentContractVersion 조합은 모든 package release에서 이 digest가 항상 같아야 합니다. publisher package release lineage에서 typed contract가 바뀌면 이전보다 큰 componentContractVersion을 사용합니다. 이 비교는 Project 설치 순서가 아니므로, 이미 등록된 과거 exact release를 나중에 다시 설치하거나 rollback하는 것은 허용합니다.

label, description, classification, category, subcategory, executorEntry, presentation과 실제 executor entry bytes는 typed digest에 넣지 않습니다. 7-field exact ComponentRef의 package version·digest가 그 변화를 이미 고정하므로 이들만 바뀐 release는 contract version을 올릴 필요가 없습니다. componentKey rename은 version bump가 아니라 새 Component identity이며 자동 alias나 유사 key fallback은 없습니다. 현재 pxflow-core::jcs_bytes에 이 세 필드 projection과 publisher release lineage registry가 아직 구현되지 않았으므로, 문서 checker의 구조 비교를 production digest로 간주하지 않습니다. 이 gate가 닫히기 전에는 lifecycle profile이 activation.v2ContainerActivated: true로 기록한 bounded local/admin Project lane보다 넓은 범위로 V2를 활성화하지 않고, production·public third-party admission은 차단합니다. inputsresults의 배열 순서는 typed contract의 일부입니다. port 순서를 바꾸면 JCS digest가 달라지므로 같은 Component identity에서는 componentContractVersion을 올려야 합니다. 이는 Handle의 순서와 authoring binding의 안정성을 보존하기 위한 의도적 선택이며 단순 표시 순서 변경으로 취급하지 않습니다.

V2의 componentKey는 package-local입니다. 전역 고유 key를 요구하지 않습니다. SDK compiler와 imported target context는 (publisher, packageKey, componentKey)를 authoring target으로 만들고, Project lock이 요청 Project의 effective Component closure에서 해당 package의 exact release를 선택해 7-field ComponentRef로 resolve합니다. 이 closure는 host-bundled admitted exact release와 Project installed closure의 닫힌 union이며 verified available release는 install 전까지 포함하지 않습니다. fixture는 서로 다른 두 package가 같은 model_check key를 제공해도 qualified target은 각각 정확히 resolve되고, host-bundled exact release도 같은 규칙으로 resolve되며, componentKey만 있는 target은 모호하거나 전역 추론이 필요하므로 거부됨을 검증합니다. 현재 V1 process registry와 key-only resolver는 그대로 보존하지만, 그 resolver만으로 V2를 활성화할 수 없습니다. qualified resolver는 product.component.resolve(scope, publisher, packageKey, componentKey)를 받아 요청 Project의 installed closure에서 exact release를 고르는 형태로 구현되어 있고 (v2MultiComponentResolverImplemented: true), 같은 package family의 release가 둘 이상이면 selection pointer가 아직 없으므로 ComponentResolveAmbiguousRelease로 거부합니다.

V2 실행 materialization은 saved target의 7-field ComponentRef 전체를 다시 검사합니다. publisher, package key/version/digest와 release byte digest는 effective closure의 exact release에, componentKeycomponentContractVersion은 stored verified declaration에 각각 exact-match해야 executor entry를 읽습니다. Project-installed source는 요청의 exact ProjectScope와 installed closure scope를 비교하고 channel acceptance·warning·audit를 다시 확인합니다. host-bundled source는 product-scope membership과 host admission을 다시 확인합니다. 두 source 모두 current RFC-005 verification/revocation evidence를 적용하며 signed verified release는 revocation evidence가 실행 시점에도 fresh해야 합니다. unsigned local/admin release는 revocation evidence를 요구하지 않고 explicit acceptance·warning 정책·audit를 다시 확인합니다. 특히 같은 release bytes라도 contract version이 다르거나, 다른 Workspace에서 같은 projectKey를 사용해도 거부합니다. worker는 이제 5-field release 비교에 더해 stored verified declaration의 componentKey·componentContractVersion을 exact-match하므로 7-field 재검증은 구현되어 있습니다(ComponentContractVersionMismatch). 남은 것은 trust 쪽입니다. row에는 acceptance channel·warning·audit가 저장되지 않아 실행 시점에 다시 확인할 수 없고 host-bundled source의 product-scope host admission 재확인 경로도 없으므로, v2ProjectScopedInstallCatalogExecutionClosureImplemented: false를 고정합니다. 이 flag는 full Project-scoped install·catalog·execution closure 한 가지에 대한 것이고, RFC-014 lifecycle profile이 기록한 activation.v2ContainerActivated: true와 모순되지 않습니다. 그 flag가 켜는 것은 이 build가 이미 서비스하는 bounded local/admin Project lane 하나뿐이며 -- 인증된 product.package.install 경로로 generation-2 Component container를 설치하고 그 contribution을 catalog하고 실행하는 것 -- public admission이 아닙니다. 이 closure가 닫히기 전에는 그 bounded lane보다 넓은 범위로 V2를 활성화하지 않고, production·public third-party admission은 위 gate 그대로 차단합니다.

선언은 closed object입니다. React module, renderer key, raw HTML/CSS, iframe, network 권한과 임의 style override를 추가하면 거부합니다. summaryResultKeys는 같은 선언의 results에 있는 key만 가리킬 수 있고 input/result 전체의 portKey는 중복될 수 없습니다.

Publisher가 쓰는 label, description과 input/result description은 NFC여야 하고 앞뒤 Unicode whitespace가 없는 single-line text만 받습니다. C0/C1 control, soft hyphen, Arabic letter mark, zero-width space, left/right mark, Unicode line separator와 bidi embedding/override/isolate·deprecated format control, BOM을 admission에서 거부합니다. 자연어와 RTL 문자는 그대로 허용하되 host는 textContent 계열만 사용하고 dir="auto"와 bidi isolation으로 그리며 bounded truncation 뒤 bounded detail에서만 전체를 보여 줍니다. publisher text와 무관한 exact publisher·packageKey· componentKey·packageVersion을 host-owned detail에 항상 두므로 표시 label이 package identity를 대신하지 않습니다. digest는 advanced detail에 유지합니다.

Input은 required·nullable, Result는 nullable을 반드시 명시하고 둘 다 optional unit을 RFC-003 canonical subset에서만 선택합니다. 현재 registry에 없는 bar, kJ/kg 같은 code는 V2가 임의로 받지 않으며 RFC-003 owner가 category·canonical conversion과 함께 확장한 뒤 사용합니다. record는 같은 선언의 required typeDefinitions.records에서 resolve합니다. duplicate/dangling key, cycle, 잘못된 rowKeyField, 오름차순이 아닌 nested enum과 record reference를 포함해 계산한 depth 16 초과는 admission에서 거부합니다.

초기 V2의 Node port는 declaration의 inputs·results로 고정합니다. instance settings나 화면의 Add input으로 port를 추가·삭제·재정의할 수 없습니다. 향후 문서 DSL의 document.input(...)을 지원하더라도 승인된 deterministic compiler가 publish/materialization 시점에 고정 declaration input으로 내리는 방식이어야 하며 Node-instance 동적 port가 아닙니다. 입력 계약이 다른 template은 한 Component의 setting variant가 아니라 같은 package가 제공하는 별도 Component declaration으로 발행합니다. instance port는 별도 versioned settings + port-projection contract 전까지 차단합니다.

분류는 한 enum이 아니라 독립된 축입니다

예시소유하는 결정
package facetBuilt-in, Installed, AvailableV2 exact release의 host-owned 탐색·설치 상태
contribution kindFunction, Component, App Surface, codec어느 product owner가 처리할지
discovery categoryreview, calculateComponents dock 검색·filter
typed port roleinputs, results, type, unit연결 가능성과 입출력 역할
topology facetsource, processor, sink, standalone선언된 port 수에서 파생하는 탐색 보조값
presentation/isolation tierhost, isolated, first-partyUI가 어디에서 실행될지

package facet의 machine key는 built-in, installed, available 세 개로 닫고 V2 Component package catalog row마다 정확히 하나만 host가 계산합니다. 모든 row는 exact ComponentRef를 갖습니다. built-in은 host가 bundled registry에 넣고 admit한 exact V2 release, installed는 그 밖의 exact release 중 요청 Project installed closure에 있는 항목, available은 verified·compatible하지만 그 closure 밖에 있는 exact release입니다. 같은 exact identity가 여러 source에 보이면 built-in > installed > available 순으로 하나만 남기고 label·category·publisher 이름으로 합치지 않습니다. 현재 V1 built-in type은 이 V2 package facet row가 아니며 기존 production 경로에 남습니다.

placement 권위는 host-bundled admitted exact release와 요청 Project installed closure만 합친 request-project-effective-component-closure입니다. 따라서 built-in도 이 effective closure의 host-bundled member인지 확인하고 host gate를 통과한 뒤에만 Place할 수 있습니다. available은 이 closure 밖이므로 placementCapability.allowed=falseinstall-required reason을 사용합니다. 설치와 Place를 한 command로 합치지 않고, 설치 후 새 catalog snapshot에서 installed가 된 다음 사용자가 별도로 Place합니다. revoked·stale·invalid는 available로 낮추지 않고 별도 trustStatus와 placement reason으로 나타냅니다. missing favorite도 일반 row가 아니라 reason이 있는 unavailable placeholder로 유지합니다. 이 facet은 trust를 소유하지 않습니다. server index와 browser filter는 세 key만 사용하며 표시 문구는 locale message로 투영하고 publisher는 facet을 지정할 수 없습니다.

“입력을 받는 Node”, “결과를 내는 Node”는 별도 renderer class나 publisher가 지정하는 Category가 아닙니다. host는 admitted declaration의 port 개수만 보고 source(input 0, result 1 이상), processor(둘 다 1 이상), sink(input 1 이상, result 0), standalone(둘 다 0)을 파생합니다. 이 topology facet은 탐색 보조값일 뿐 연결 가능성, 실행 순서, renderer 또는 capability를 결정하지 않습니다. 특히 source는 trigger나 execution root, sink는 terminal execution Node를 뜻하지 않으며 실행 role을 추론하지 않습니다. 결과 viewer는 보통 review task category를 사용하지만 selection 같은 result를 다시 내보낼 수 있으므로 topology만으로 viewer라고 추측하지 않습니다. iframe은 Category가 아닙니다. 격리 수단이며 capability나 renderer를 선택하지 않습니다.

1차 task category는 V2에서만 닫힌 여섯 개입니다.

key사용자가 찾는 작업
input외부·수동·artifact 값을 Flow에 넣기
transformtype·shape·unit·mapping 바꾸기
calculate계산·해석·모델 실행하기
review결과·모델·표·품질 확인하기
deliver문서·파일·외부 destination으로 보내기
control분기·병합·반복·실행 순서 제어하기

subcategory는 optional publisher semantic key입니다. Category는 탐색 metadata일 뿐이라 renderer를 고르거나 capability를 주지 않습니다. V1의 viewschecks를 V2 category로 자동 추측하지 않습니다. publisher가 V2 release를 만들 때 의도에 맞는 category를 선택합니다.

Components catalog는 배열이 아니라 bounded query입니다

현재 production Dock은 전달받은 nodeTypeOptions 전체를 동기 map·filter하고 검색 결과를 모두 mount합니다. built-in 수가 작은 현재 V1에는 맞지만 package당 256개까지 허용하는 V2의 activation-ready 구현은 아닙니다. V2는 이 구현을 그대로 확장하지 않고 bounded-snapshot-paged-indexed-query를 host Flow application의 activation requirement로 둡니다. versioned request/response schema는 component-catalog-wire@1이 소유하고 stale cursor를 포함한 승인 fixture도 함께 고정되어 있습니다. schema를 소유해도 활성화되지 않습니다. host Flow application이 이 query를 실제로 구현하기 전에는 이 요구를 구현 완료 API로 부르거나 V2를 활성화하지 않습니다.

Project-scoped federated discovery index는 host-bundled admitted exact release, Project installed closure와 verified·compatible available exact release를 같은 snapshot view로 합칩니다. 실제 placement 권위는 host-bundled admitted exact release와 요청 Project installed closure의 닫힌 effective union뿐입니다. index는 검증된 declaration에서 exact ComponentRef, label, description, category, subcategory와 icon을 row로 만듭니다. 각 row에는 host가 install state, RFC-005 verification/revocation, runtime availability와 icon/renderer presentation admission에서 계산한 placementCapability (allowed와 stable reason code) 및 trustStatus도 포함합니다. 둘은 publisher가 지정할 수 없고, UI는 reason code를 locale message로 바꿉니다. 따라서 unavailable exact favorite나 revoked Component도 page 밖 declaration을 N+1 조회하지 않고 안전하게 disabled row로 표시할 수 있습니다. browser 첫 payload는 favorite exact ref와 첫 page만 받고 full declaration은 detail, placement 또는 exact resolution 때 lazy load합니다. query는 index snapshot revision, search, category, package facet, opaque cursor와 limit을 받습니다. 검색은 label·description·category·subcategory·publisher·package/component key의 Unicode NFKC default case-fold index를 사용합니다. 정규화와 정렬 owner는 같은 project-scoped-federated-component-discovery-index이며 browser는 결과를 재정렬하지 않습니다. 문자열은 locale collation이나 UTF-16 비교가 아니라 선언된 정규화 뒤 unsigned UTF-8 byte lexicographic order, componentContractVersion은 numeric ascending으로 비교합니다. packageVersion은 이 tuple에서 문자열 tie-break이며 SemVer precedence로 조용히 재해석하지 않습니다. normalized label 뒤 exact publisher/package/component/version/digest tuple로 안정 정렬하고 cursor를 snapshot revision과 마지막 sort tuple에 묶습니다. cursor는 client에 opaque라 browser가 decode하거나 tuple을 재구성하지 않습니다. snapshot이 바뀐 stale cursor는 거부하고 첫 page부터 다시 읽습니다. Unicode data version 또는 normalizer implementation digest가 아직 고정되지 않았으므로, 이를 고정하기 전에는 JS localeCompare, Intl.Collator, toLocaleLowerCase< 기반 fixture로 정렬이 재현된다고 주장하지 않고 V2 activation을 막습니다.

기본 page는 40개, 최대 80개입니다. 새 query가 오면 이전 요청을 abort하고 latest query만 commit합니다. 결과가 33개 이상이면 measured row geometry, overscan 8, mounted row 32개 budget으로 virtualize합니다. desktop palette의 현재 CSS 권위는 최대 block size 420px, row 최소 block size 58px입니다. 따라서 보이는 8행(ceil(420/58)) + 양쪽 overscan 16행 + off-screen focused row 1행 = 최대 25행으로 32행 절대 상한 안에 들어옵니다. 32개는 active DOM-focused row까지 포함한 절대 상한이며 focus pin 때문에 넘기기 전에 가장 먼 overscan row부터 제거합니다. 이 CSS 값이나 palette 상한을 바꾸면 같은 부등식과 benchmark를 다시 승인해야 합니다. 첫 ResizeObserver 측정 전에도 58px를 estimate로 사용해 전체 page를 먼저 mount하지 않습니다. query/page/filter가 바뀌면 같은 exact ref의 새 row button이 있으면 그곳으로, 없으면 programmatic-focusable results container로 focus를 옮긴 뒤 이전 row를 unmount합니다. logical exact-ref identity 유지가 DOM mount 유지를 뜻하지는 않습니다. 작은 목록은 native li > button tab order를 사용합니다. virtualized 목록에서도 listitem 자체를 focusable하게 만들지 않고 row button 하나가 roving tabindex를 소유하며 aria-setsize·aria-posinset은 listitem metadata로만 제공합니다.

현재 account-scoped device preference V1의 flowFavoriteNodeTypeIds: string[]와 unavailable id를 버리는 normalization은 그대로 보존하되 third-party V2 identity로 확대하지 않습니다. V2는 별도 flow-component-favorites@2 envelope를 계획합니다. entry는 built-in type id 또는 exact 7-field ComponentRef를 discriminant로 나누고 최대 7개를 순서대로 저장합니다. Component favorite는 자동 update resolution을 하지 않습니다. missing, revoked, uninstalled ref도 조용히 지우거나 비슷한 Component로 바꾸지 않고 reason이 있는 unavailable placeholder로 유지합니다. 교체는 사용자가 새 exact ref를 고를 때만 하며 favorite lookup은 현재 page와 독립적으로 exact 조회합니다. V1에서는 알려진 built-in id만 순서를 보존해 import하고 third-party string을 추측하지 않습니다. 이 envelope schema가 아직 없으므로 schema 승인 전 V2 favorite 저장과 activation은 금지합니다.

별도 contract-only component-v2-catalog-benchmark@1은 1440×900 desktop viewport와 420px palette cap·58px minimum row를 이름 있는 단일 scenario로 고정하고, seed 1739의 10,000 Component fixture에서 첫 page open, 여섯 category filter, 세 글자 prefix 검색, next page와 keyboard paging을 warm-up 3회, 측정 10회 실행합니다. baseline/candidate build digest와 동일한 fixture digest를 함께 증거로 남기고 operation별 repetition 값을 nearest-rank p95로 줄이며 scenario 사이 값을 pooling하지 않습니다. 두 실행은 runner, Playwright, axe-core, Chromium channel·browser, OS, CPU·memory, DPR, build mode와 React Flow version이 모두 같아야 합니다. 선언된 모든 operation과 repetition이 error·timeout 없이 끝나지 않은 실행은 일부 결과가 budget 안에 들어와도 통과시킬 수 없습니다. query p95와 palette commit은 같은 환경 baseline의 1.05배 이하, mounted row 32개, initial row 40개, initial full declaration payload 0개, 접근성 회귀 0개여야 합니다. runner가 아직 구현되지 않았으므로 current eager Dock이나 ad-hoc 측정으로 V2 activation을 승인할 수 없습니다. 또한 generator source, PRNG, exact corpus/query probe와 scenario별 canonical golden fixture digest가 아직 미소유 상태라 seed와 generator 이름만으로 재현 가능하다고 주장하지 않습니다. 이 네 authority도 runner와 함께 고정해야 숫자 budget이 활성화됩니다.

presentation과 외부 UI 경계

tierCanvaspublic third-party상태
host_declarative허용허용V1 metadata를 저장하고 production bridge도 구현됨, V2는 component.declarative@2
isolated_rich_surface금지RFC-014 뒤 별도 admission아직 선언·활성화 금지
first_party_native허용금지product와 함께 build·review하는 내부 renderer

Canvas에서 외부 Component Node 하나가 만드는 third-party executable module, iframe, raw HTML/CSS, network request, timer와 observer 수는 모두 0입니다. typed Handle, selection, execution state, theme, focus, 접근성과 drag hot path는 host가 소유합니다.

V2 presentation.icon은 production ICONOGRAPHY_CONTRACT.publisherFacingKeys.publicKeys에 등록되고 실제 glyph가 있는 key만 host surface가 materialize하며 unknown icon을 대체 glyph로 바꾸지 않습니다. Rust installer는 frontend ESM registry를 복제하지 않고 semantic-key shape와 signed declaration만 검증합니다. host catalog/Node projection이 iconography@1 key-set digest를 확인한 뒤 unknown key의 exact ComponentRef를 unsupported diagnostic으로 보냅니다. digest는 registry order의 string array를 JSON 직렬화한 UTF-8 bytes에 SHA-256을 적용합니다 (sha256-utf8-json-array-registry-order). 이 publisher-facing inventory는 iconography@1이며 key 집합은 정확히 고정됩니다. key 추가와 제거 모두 새 iconography contract와 이를 소비하는 host renderer contract를 함께 요구합니다. deprecated key도 기존 선언을 위해 glyph와 rendering을 유지합니다. package가 design-system version을 복제해 저장하지는 않습니다. package icon은 identity decoration일 뿐 trust, verification, capability, execution status나 diagnostic severity를 뜻하지 않습니다. check, circle-alert, circle-check, lock, lock-keyhole, shield-check, triangle-alert는 host badge용 reserved key라 publisher가 선택할 수 없고, trust/status는 host-owned text와 badge로만 표시합니다. iconography@1에서 neutral fallback을 두지 않는 것은 누락된 glyph를 다른 Component identity처럼 보이게 하지 않기 위한 의도적 strict parity 선택입니다. 완화가 필요하면 기존 renderer를 조용히 바꾸지 않고 새 iconography/renderer contract에서 결정합니다. component.declarative@2의 border box는 선언한 port 수가 고르는 세 registered tier 중 하나입니다. 한쪽 2 port까지 296×224px, 4 port까지 296×316px, 8 port까지 296×408px이며 port row start 48px, pitch 46px를 사용합니다. input/result는 각 8개, 총 16개까지만 받아 모든 Handle이 box 안에 보이게 합니다. tier는 registry가 정하는 수이고 renderer instance가 계산하지 않으며 port를 숨기지도 않습니다. 더 큰 interface는 record·table로 묶습니다. tier 표나 geometry를 바꾸면 새 renderer contract version이 필요합니다.

presentation.summaryResultKeys는 live value source가 아니라 declaration result 중 Canvas에서 요약할 후보와 순서를 고르는 metadata입니다. 첫 component.declarative@1은 이 key와 declared type·unit label만 그렸고, renderer contract version 2는 같은 key의 값을 현재 Run의 result snapshot에서 host가 읽어 선언된 type·unit으로 formatting해 그립니다. package는 renderer, markup 이나 formatter를 공급하지 않습니다. 값이 없으면 declared key와 type·unit label만 그리고 loading, error, stale state는 그리지 않습니다. 특히 list, table, calculation_trace를 inline 값으로 해석하지 않습니다. inline value kind를 넓히려면 scalar kind allowlist, canonical formatting, loading/error/stale semantics와 row/byte bounds를 소유하는 새 versioned value-projection contract를 먼저 승인합니다.

표·chart·3D·문서처럼 넓은 UI는 Node body를 키우지 않고 Open editor로 엽니다. 이 isolated_rich_surface의 entry bytes, message envelope와 bridge capability는 RFC-016의 component-rich-surface-declaration@1·component-rich-surface-bridge@2가 소유하며 이 profile이 다시 정의하지 않습니다. capability registry, 자원 상한과 revoke·upgrade는 여전히 RFC-014가 소유하고, 이 tier는 Canvas에 mount하지 않으며 public third-party admission도 그대로 막혀 있습니다. iframe sandbox가 CPU·memory budget을 대신하지 않는다는 원칙도 RFC-014의 별도 resource admission으로 검증합니다. 따라서 interactive model viewer, interactive result table, iframe embed는 review 같은 task category의 변형이 아닙니다. 모두 isolated rich-surface 대상이며, 현재 V2 declaration과 component.declarative@2으로 요청하면 이름 있는 거부를 받습니다.

Canvas 성능 계약

install 때 raw declaration bytes가 pxflow-core::jcs_bytes의 RFC 8785 canonical bytes와 같은지, 그리고 manifest digest가 그 exact bytes를 가리키는지 한 번 검증하고 exact packageDigest + releaseByteDigest + componentKey + componentContractVersion으로 resolution을 cache합니다. 검색 index도 install 때 갱신하므로 drag·pan·zoom 중 package I/O나 module 평가가 없습니다.

React Flow Node renderer는 memoize하고 Node body가 전체 nodes/edges collection을 구독하지 않게 합니다. node-local selector를 유지합니다. 현재 FlowSurface는 surface-wide onlyRenderVisibleElements threshold를 read-only 100 Node/150 Edge, authoring 250 Node/300 Edge로 이미 적용합니다. V2 renderer만 이를 끌 수는 없으므로 현재 threshold를 상속하되, React Flow가 밝히는 culling overhead까지 포함해 아래 benchmark로 재승인합니다. 통과하지 못하면 V2를 켜기 전에 surface owner 계약과 구현을 함께 바꿉니다. container 하나가 Component를 여러 개 제공해도 provider, iframe 또는 event listener를 Component 수만큼 만드는 구조가 아닙니다.

install/index 자원은 container당 Component 256개, declaration raw entry당 256 KiB, raw bytes와 canonical bytes를 동일하게 요구한 declaration 합계 4 MiB, manifested Python entry 512개, entry당 1 MiB, container 합계 16 MiB, Component당 input/result 각 8개·합계 16개, record definition 256개·record당 field 512개, resolved type descriptor depth 16, summary result 4개로 제한합니다. label 120자, description 2,000자, port description 500자, icon key 64자, executor path 512자 상한도 schema·checker가 함께 검증합니다. port가 더 필요하면 많은 Handle 대신 record·table type으로 묶습니다.

V2 activation 전 generator component-v2-500-node@1, seed 1739, row-major 25×20 layout과 gap 240×160을 고정합니다. 독립 variant 배열을 조합하지 않고 아래 세 scenario row만 실행합니다.

scenariotopologyEdge/Nodevisibleportssummaryviewport
forward-chain-culling-lightforward-chain-seeded-shortcuts10.1401440×900
clustered-fanout-denseclustered-fanout-10x5040.51641440×900
forward-chain-all-visible-wideforward-chain-seeded-shortcuts111641920×1080

표에 없는 조합을 Cartesian, pairwise 또는 runner 재량으로 자동 생성하지 않습니다. 현재 generator source, PRNG, scenario별 exact Node/edge/port/summary 분포와 golden fixture digest, interaction target ID는 아직 소유되지 않았습니다. 따라서 이 표와 seed는 activation plan이며 재현 가능한 release gate라는 주장은 금지합니다. 이 authority를 모두 pin한 뒤에만 runner가 숫자 budget을 판정합니다.

  1. Node 0을 320×160 CSS px, 800ms, 48 step으로 drag
  2. viewport를 -640×0 CSS px, 800ms, 48 step으로 pan
  3. zoom을 1→0.65→1, 1,000ms, 60 step으로 변경

각 scenario를 warm-up 3회 뒤 10회 측정합니다. sampling window는 첫 interaction부터 마지막 frame 500ms 뒤까지입니다. browser 이름·version, OS, logical CPU, device memory, device pixel ratio, build commit/mode와 React Flow version뿐 아니라 runner, Playwright, axe-core, bundled Chromium channel/version을 같은 evidence에 저장합니다. buildCommit은 진단 정보일 뿐 build identity가 아닙니다. baseline/candidate 각각의 RFC-002 content digest와 fixture digest를 기록하고, 양쪽 fixture digest가 서로 같고 해당 scenario golden digest와도 같아야 비교를 시작합니다.

측정 owner는 contract-only component-v2-canvas-benchmark@1입니다. 이 runner가 구현되지 않았으므로 ad-hoc DevTools 측정만으로 V2 activation을 승인할 수 없습니다. 동일 machine·browser build·tool version에서 baseline과 candidate를 함께 실행하고, hidden document, focus 손실, viewport/DPR 불일치, tool version 불일치 또는 GC/Long Task API 부재가 있으면 run 전체를 무효로 합니다.

  • frame time: 각 interaction sampling window의 연속 requestAnimationFrame 시작 시각 delta
  • long task: PerformanceObserver가 수집한 duration 50ms 이상 longtask entry 수
  • Node render: Strict Mode를 끈 production build에서 component.declarative@2 layout-effect commit counter
  • heap: 별도 pass에서 전후 강제 GC 뒤 CDP runtime used size의 max(0, after - before)
  • accessibility: 같은 axe-core version과 WCAG 2 A/AA, 2.1 A/AA, 2.2 AA tag로 Canvas fixture를 검사한 violation node 합계

각 repetition의 raw metric과 axe output을 보존합니다. frame time은 scenario × interaction × repetition 안에서 nearest-rank p95를 한 값으로 만들고, 10개 repetition 값도 nearest-rank p95로 줄입니다. 나머지 metric은 scenario × repetition당 한 값을 만들고 같은 방식으로 줄입니다. scenario끼리 pooling하거나 평균내지 않으며 모든 scenario가 각각 통과해야 합니다.

승인된 동일 환경 baseline 대비 각 scenario의 drag·pan·zoom frame p95와 Node render count는 1.05배를 넘을 수 없습니다. long task와 접근성 회귀 count는 baseline보다 하나도 늘 수 없습니다. heap delta는 계속 수집하지만 forced-GC noise envelope를 실제 계측·승인하기 전에는 1.10 같은 임의 비율이나 baseline 0 규칙을 적용하지 않습니다. non-heap ratio의 baseline이 0이면 candidate도 0이어야 합니다. 이는 library 간 보편 속도 순위가 아니라 V2를 추가한 build가 현재 품질을 퇴행시키지 않는지 판정하는 상대 gate입니다.

  • drag frame time
  • pan frame time
  • zoom frame time
  • long task count
  • Node render count
  • heap delta
  • accessibility regression count

상대 gate만 통과한 느린 baseline도 승인하지 않습니다. 기존 Flow machine owner인 pipelineXlab_pxflow/apps/pxflow-web/performance-budget.v1.json이 scenario × interaction 절대 UX budget을 binding하고, 같은 owner와 runner가 forced-GC heap noise envelope를 계측·승인해야 합니다. 두 결정은 현재 미소유 상태이며 숫자를 이 문서에서 발명하지 않습니다. baseline 회귀가 없더라도 이 두 activation blocker가 닫히기 전에는 V2를 열 수 없습니다.

migration과 실패 정책

  • V1 container → V2 container는 publisher가 새 release를 발행하는 명시적 전환입니다. in-place 자동 migration은 없습니다.
  • 기존 V1 container release와 .pxflow의 exact ComponentRef는 계속 pinned 상태로 읽습니다.
  • V1 settings와 presentation을 없애지 않습니다. 이를 잇는 V2 setting/rich contract의 owner가 정해질 때까지 V1 그대로 둡니다.
  • V2 선언에는 setting schema가 없으므로 Node settings는 생략하거나 빈 object만 허용합니다. 현재 .pxflow V1 componentNode schema는 기존 Component를 위해 non-empty settings를 계속 읽습니다. V2 resolver가 effective Project closure release의 requiredContractVersion: 2와 exact target을 join한 뒤 별도 semantic gate에서 member 생략과 {}는 받고 non-empty object는 거부합니다. V1 schema를 전역으로 좁히지 않습니다. settings가 있는 V1 Component는 versioned setting contract의 owner·validation·migration이 승인되기 전 V2로 republish할 수 없습니다.
  • V2를 모르는 현재 installer는 requiredContractVersion: 2를 거부합니다.
  • 아래 recovery 항목의 scope는 generic Component와 successor V2 only이며 current V1 Engineering Paper domain setting의 legacy contractSnapshot read/persistence를 포함하지 않습니다. 지원하지 않는 declaration version과 unknown member는 fail closed 처리합니다. 원본 package bytes는 package artifact owner가 실제로 보존한 경우에만 별도 recovery evidence로 사용하며 Flow 저장값에서 추론하지 않습니다. current reader는 saved exact ComponentRef, raw settings와 usage-derived partial ports를 보존합니다. generic/successor recovery에는 full/last-verified typed-port snapshot persistence owner가 없으므로 새 snapshot 보존을 약속하지 않고 read-only unsupported diagnostic을 표시합니다.
  • 비슷한 이름의 Component, category 또는 renderer로 fallback하지 않습니다.
  • V1 installer의 deny-all 실행 권한 정책을 그대로 상속합니다. 별도 versioned capability contract와 sandbox enforcement가 승인되기 전에는 decoded manifest capability 목록이 비어 있는 container만 admission합니다. V1 canonical writer처럼 capabilities member를 생략하거나 명시적 []를 쓰는 두 표현은 같은 빈 목록이며, network를 포함한 모든 명명된 실행 권한 요청을 거부합니다. 서명 검증 통과는 실행 권한을 부여하지 않습니다.
  • 현재 V1 dependency range 수용은 보존하지만 V2 decoded pythonDependencies는 빈 목록만 admission합니다. wire의 생략과 명시적 []는 같은 빈 목록으로 해석합니다. exact dependency lock·SBOM·artifact digest·runtime materialization 계약이 versioned authority로 승인되기 전에는 package dependency를 V2 실행 경로에 열지 않습니다.

2026-08-30 공식 library 비교

버전은 검토 시점을 고정하기 위한 값이며 PipelineXLab dependency pin이 아닙니다.

공식 생태계확인한 방식적용 판단
React Flow (12.11.5)custom Node를 host renderer map으로 등록, memoization·좁은 subscription·단순 styleinteraction/render adapter로 유지. package trust나 외부 UI 권한은 맡기지 않음
Node-RED (5.0.4)npm package 하나가 Node 여러 개를 등록, palette와 edit template 분리package 1 → Component N과 discovery/editor 분리 채택
n8n (2.36.8)community node code가 host와 workflow data에 주는 위험을 명시install trust, executor sandbox와 UI isolation을 서로 다른 gate로 유지
VS Code (1.135)package.json manifest가 contribution을 선언하고 activation event가 필요한 시점의 activate()를 호출. Extension Host 분리, webview 최소 사용·격리·message passingmanifest-first discovery와 future rich surface의 explicit lazy activation 채택
ComfyUI (0.34.0)V3 ComfyExtension.get_node_list()가 Node class 여러 개를 반환, registry가 immutable version·workflow pin·security scan을 관리package 1 → Node N과 exact version pin은 구조 참고로 채택. V3 v0_0_2 API는 공식 문서상 아직 개발 중이므로 PipelineXLab 호환성 권위로 pin하지 않음. browser JS의 전역 load 방식도 Canvas 격리 규칙 때문에 채택하지 않음
Langflow (1.11.5)pip-installable extension이 하나 이상의 bundle과 그 Component들을 등록하고 closed extension.json manifest가 ID·version·compatibility·bundle path를 선언package와 Component의 1:N, provider와 task category의 분리 및 manifest-first validation을 뒷받침. 특수 custom icon의 추가 frontend wiring은 PipelineXLab의 host-owned icon registry에 들이지 않음
Rete.js (2.0.6)typed plugin scopecomposition 참고만 함. security boundary로 쓰지 않음
LiteGraph (1.0)Canvas2D Node registry대규모 drawing 참고만 함. 현재 React·DOM 접근성 기반을 교체하지 않음

가장 나은 조합은 Node-RED·ComfyUI·Langflow의 package-many-contributions, VS Code의 manifest·lazy activation, React Flow의 host renderer·memoization을 합치는 것입니다. Rete.js나 LiteGraph로 기반을 바꿔도 signature, capability, third-party trust는 해결되지 않으므로 지금 migration할 이유가 없습니다. 공식 생태계가 같은 Flow 연결 구조·browser·device로 측정한 cross-library benchmark는 제공하지 않으므로 “React Flow가 절대 가장 빠르다”고 주장하지 않습니다. 현재 React/DOM 접근성, 기존 interaction contract와 migration cost를 포함한 PipelineXLab 적합성 판단이며 실제 V2 성능은 위의 고정 500-Node protocol로만 승인합니다.

구현 gate

V2 구현 순서는 ComponentDescriptor v2 compiler와 schema URI replacement 뒤 RFC 8785 JCS projection, package reader의 requiredContractVersion: 2 dispatch, exact declaration/Python content closure, resource·RFC-003 type/unit·RFC-005 trust admission, empty pythonDependencies, effective Project closure release와 exact ComponentRef join, package-qualified authoring resolution 및 실행 시 stored declaration까지의 full-ref 재검증, RFC-8785-JCS typed compatibility digest projection과 publisher package release lineage registry, helper-bearing package를 위한 별도 versioned runtime Python module ABI·worker materialization contract, Project-installed branch의 exact ProjectScope와 host-bundled branch의 product-scope provenance를 각각 보존하는 install → local/Postgres storage → catalog/detail → execution closure 및 cross-workspace-same-project conformance, component.declarative@2 registry·bridge, component-declared-settings@1 join gate, 이미 소유한 component-catalog-wire@1 schema를 따르는 versioned catalog query와 stale-cursor 처리 구현, pinned Unicode normalizer/sort authority, flow-component-favorites@2 schema, pinned 10,000-Component catalog runner, pinned named-scenario 500-Node Canvas runner, 기존 Flow owner의 absolute UX budget binding과 measured heap noise envelope입니다. 이 목록을 요약본으로 독자적으로 늘리지 않고 machine profile의 모든 activationAllowedWithout…: false 항목을 exhaustive blocker 목록으로 사용합니다. 각 단계의 conformance evidence가 모두 생기기 전에는 profile을 production으로 바꾸지 않습니다. 여기에 RFC-014 승인도 함께 필요하며 둘 중 하나만 통과해서 activation할 수 없습니다.

public rich surface, general setting schema, runtime renderer registry mutation과 external native React는 이 V2 범위에 포함하지 않습니다. 해당 owner contract와 admission이 승인되기 전 parser field부터 미리 열지 않습니다.

rich surface의 owner contract는 더 이상 이름 없는 future RFC가 아니라 RFC-014의 successor인 RFC-016입니다. RFC-016은 자신의 네 surface(component-rich-surface-declaration@1 grammar, component-rich-surface-resolution@1 wire, content-addressed GET custody route와 component-rich-surface-bridge@1 bound)만 소유하며 RFC-014의 어떤 것도 승인하지 않습니다. RFC-014 §8의 rich plugin ABI 배제는 그대로이고 이 successor로 향하는 forward link만 갖습니다. 이 profile의 richSurface.declarationAllowedactivationAllowed는 계속 false이며, blockedBydeferred-beyond-rfc014로 남습니다.