조직·워크스페이스·접근 정책
이 문서는 PipelineXLab에서 사용자가 여러 조직과 워크스페이스에 참여하고 Project, PXFLOW, Run·Result와 설치한 Component의 artifact, 관리 기능에 접근할 때 사용하는 정식 계약을 정의합니다.
핵심 원칙은 다음과 같습니다.
Organization은 사용자에게 보이는 조직이면서 저장·인가·cache·audit의 tenant isolation boundary입니다. Organization과 tenant는 항상 1:1이며 별도 tenant identity를 중복 저장하지 않습니다.- 한 principal은 여러 Organization에 참여할 수 있지만, 한 command는 정확히 하나의 active Organization context에서 실행됩니다.
OrganizationMembership,WorkspaceMembership과 Project/ArtifactAccessGrant는 서로 다른 권한 층입니다.- Organization 또는 Workspace 관리자라는 이유만으로 Project나 artifact 본문을 읽을 수 없습니다.
- 사용자에게 보이는 role은 versioned capability preset입니다. 최종 권한 원본은 scope가 명시된 canonical capability입니다.
- Project role은
Owner,Admin,Editor,Commenter,Viewer다섯 개뿐입니다.Reviewer는 role이 아니라 exactReviewRequest의 assignment입니다. EntitlementLease는 제품 이용 자격을 증명합니다. 범위 없는roles[], Project role, Workspace role 또는 AccessGrant를 저장하지 않습니다.- 모든 관리자 변경은 Plan → 사용자 확인 → Apply를 사용합니다. role, grant, policy와 ownership을 generic patch로 변경하지 않습니다.
- membership, group, grant, policy, guest와 link session 변경은 영향을 받는 Organization/Workspace epoch 또는 Project/Artifact access generation을 바꾸고 기존 cache, ticket와 session을 다시 검증하게 합니다.
- portable artifact에는 Organization, membership, grant, role, token, audit와 policy를 넣지 않습니다.
- Project Catalog의 모든 visible Project는 requesting principal에 대한 effective
AccessGrant를 가져야 합니다. Organization 또는 Workspace membership만으로는 Project가 보이지 않습니다.
Account principal
├─ OrganizationMembership ── Organization (= tenant boundary)
│ └─ WorkspaceMembership ── Workspace
│ └─ AccessGrant ─────── Project 또는 Artifact
└─ 다른 OrganizationMembership ── 다른 tenant boundary소유 범위
이 문서는 다음 의미의 유일한 정식 계약입니다.
- Organization과 tenant의 관계
- active Organization/Workspace context
- OrganizationMembership과 WorkspaceMembership
- organization/workspace/project role preset과 capability
- authenticated guest, Group과 link-derived session의 접근 자격
- Project/Artifact AccessGrant와 effective authorization
- Project/Artifact access-state lifecycle과 authorization generation
- 관리자 Plan/Apply, ownership 보호와 authorization invalidation
- access·policy audit의 조회, 내보내기, redaction, retention과 legal hold
다음 계약은 각 owning 문서가 소유합니다.
- semantic key와 opaque internal ref: 이름·ID·참조
- Report/PDF output action, RenderProfile과 snapshot pin: Report output과 외부 전달
- Publication, ShareLink delivery와 EmbedDelivery: Publication과 EmbedDelivery
- TemplateRelease, Marketplace 사용 권리와 Organization Library 배정: Template Marketplace와 Organization Library
- 사용자 승인과 실행 안전성: 보안·권한·승인
- Local 파일과 Cloud copy의 사용자 흐름: 로컬·클라우드·공유
Organization과 Workspace
Organization은 tenant boundary다
organizationRef는 Organization의 opaque internal identity이자 tenant isolation key입니다. 별도 tenantId, tenantRef 또는 provider account ID를 같은 domain object에 추가하지 않습니다.
{
"organizationRef": "<opaque organization ref>",
"kind": "managed",
"displayName": "Structural Engineering Team",
"state": "active",
"policyRevision": 7,
"authorizationEpoch": 31,
"generation": 12
}| field | required | 규칙 |
|---|---|---|
organizationRef | 예 | repository가 발급한 opaque ref. provider ID, email, domain과 display name에서 만들지 않음 |
kind | 예 | personal 또는 managed |
displayName | 예 | 변경 가능한 사용자 표시값. identity나 authorization이 아님 |
state | 예 | active, suspended, closed 중 하나 |
policyRevision | 예 | 적용 중인 OrganizationPolicy revision |
authorizationEpoch | 예 | 조직 범위 인가 무효화용 단조 증가 정수 |
generation | 예 | Organization 관리 변경의 optimistic concurrency token |
Organization의 database row, object namespace, encryption boundary, cache/search key, audit와 backup inventory는 모두 같은 organizationRef로 scope됩니다. 서로 다른 Organization 사이에서 object dedup, membership, grant, session과 cache entry를 공유하지 않습니다.
Organization lifecycle은 active ⇄ suspended → closed입니다. suspended에서는 audit, ownership recovery와 허용된 data recovery 외의 productive command를 거부합니다. closed는 terminal이며 같은 ref를 새 Organization으로 재활성화하지 않습니다.
AWS account, Cognito subject, database schema, bucket, filesystem path와 deployment region은 Organization identity가 아닙니다.
Personal Organization과 Personal Workspace
계정이 처음 productive context를 만들 때 다음 항목을 하나의 bootstrap transaction으로 생성하거나 이미 존재하는 항목을 resolve합니다.
- principal 소유의
kind=personalOrganization 하나 - 그 Organization 안의
kind=personalWorkspace 하나 - principal의 active owner OrganizationMembership
- principal의 active owner WorkspaceMembership
Personal Organization도 다른 Organization과 같은 격리·membership·policy·audit 계약을 사용합니다. if personal then allow, if local then admin 또는 파일 소유자를 권한으로 사용하는 예외를 만들지 않습니다.
Personal Organization과 Workspace identity는 email, OS username, home path와 installation ID에서 만들지 않습니다. 이름이 바뀌거나 다른 장치에서 로그인해도 같은 server-owned personal context를 resolve할 수 있습니다. Local-only repository는 account principal에 대응하는 opaque local ref를 사용하고 portable 파일에는 그 ref를 기록하지 않습니다.
Workspace
Workspace는 정확히 한 Organization에 속합니다.
{
"workspaceRef": "<opaque workspace ref>",
"organizationRef": "<same tenant boundary ref>",
"workspaceKey": "structural_team",
"kind": "team",
"displayName": "Structural team",
"state": "active",
"policyRevision": 4,
"authorizationEpoch": 18,
"generation": 9
}workspaceKey는 Organization 안에서 고유한 semantic key이고 공통 key grammar를 따릅니다. Workspace를 다른 Organization으로 in-place 이동하지 않습니다. 다른 Organization으로 전달할 때는 export/copy/import로 새 identity와 lineage를 만듭니다.
Workspace kind는 personal 또는 team closed enum입니다. Personal Organization의 bootstrap Workspace만 personal을 사용할 수 있고, 일반 관리 Workspace는 team을 사용합니다.
Workspace lifecycle도 active ⇄ suspended → closed입니다. parent Organization이 suspended/closed이면 Workspace state와 무관하게 productive command를 거부합니다. closed Workspace ref를 다른 Organization이나 새 Workspace에 재사용하지 않습니다.
Principal과 active context
Principal
외부 로그인 identity는 (issuer, subject)에서 하나의 opaque principalRef로 resolve합니다. email, display name, organization name과 login provider username은 변경 가능한 profile이며 identity가 아닙니다.
human principalRef는 PipelineXLab 계정의 영속 제품 identity입니다. 같은 사람을 가리키는 별도 accountRef를 중복 생성하지 않습니다. 한 principal은 여러 로그인 수단을 가질 수 있으며 각 연결은 다음 ExternalIdentityBinding으로 관리합니다.
{
"externalIdentityBindingRef": "<opaque binding ref>",
"principalRef": "<principal ref>",
"issuer": "https://identity.example.com",
"subject": "<provider subject>",
"providerConnectionRef": "<optional organization SSO connection ref>",
"state": "active",
"generation": 1
}- active
(issuer, subject)는 전체 제품에서 하나의 principal에만 결합합니다. - email, 표시 이름과 도메인이 같다는 이유로 principal을 자동 병합하지 않습니다.
- SSO provider 교체나 두 로그인 수단 연결은 기존 principal에 binding을 추가·검증하는 절차이며 새 principal을 만들지 않습니다.
- provider identifier는 로그인 연결 값일 뿐 Organization, Project, artifact와 감사 actor identity가 아닙니다.
계정 보안 상태도 OrganizationMembership과 분리합니다.
{
"principalSecurityStateRef": "<opaque security state ref>",
"principalRef": "<principal ref>",
"state": "active",
"securityEpoch": 4,
"generation": 4
}state는 active, security_suspended, closure_pending, closed입니다. 전역 계정 보안 정지는 계정 본인, identity provider 또는 account control plane이 소유하며 Organization Admin이 다른 Organization까지 영향을 주도록 변경할 수 없습니다. Organization Admin의 일시 정지는 아래 OrganizationMembership 상태만 바꿉니다.
이름, 이메일, avatar, identity provider label과 마지막 동기화 시각은 mutable profile projection입니다. 조직 People 화면은 현재 OrganizationMembership이 있는 principal의 허용된 profile만 조회하며 profile 값을 권한 원본, 파일 key나 감사 actor key로 사용하지 않습니다.
principal kind는 다음 closed enum을 사용합니다.
humanservicedevice
Authenticated guest도 별도 임시 principal 문자열을 만들지 않고 human principal을 사용합니다. guest 여부와 허용 범위는 OrganizationMembership의 membershipKind=guest가 소유합니다.
한 command의 active context
한 principal이 여러 Organization에 속할 수 있으므로 모든 application command와 protected query는 command scope에 맞는 다음 closed union 중 하나를 사용합니다.
OrganizationCommandContext = {
kind: "organization",
principalRef,
principalSecurityEpoch,
organizationRef,
authenticationSessionRef,
organizationAuthorizationEpoch
}
WorkspaceCommandContext = {
kind: "workspace",
principalRef,
principalSecurityEpoch,
organizationRef,
workspaceRef,
authenticationSessionRef,
organizationAuthorizationEpoch,
workspaceAuthorizationEpoch
}
ProjectCommandContext = {
kind: "project",
principalRef,
principalSecurityEpoch,
organizationRef,
workspaceRef,
projectRef,
authenticationSessionRef,
organizationAuthorizationEpoch,
workspaceAuthorizationEpoch,
projectAccessGeneration
}
ArtifactCommandContext = {
kind: "artifact",
principalRef,
principalSecurityEpoch,
organizationRef,
workspaceRef,
projectRef,
artifactRef,
authenticationSessionRef,
organizationAuthorizationEpoch,
workspaceAuthorizationEpoch,
projectAccessGeneration,
artifactAccessGeneration
}정확한 규칙:
- Organization 관리와 audit query는
OrganizationCommandContext를 사용하며 active OrganizationMembership만 요구합니다. 관련 없는 Workspace를 선택하거나 WorkspaceMembership을 만들도록 요구하지 않습니다. - 모든 authenticated context는 current
principalSecurityEpoch을 pin합니다. principal이security_suspended,closure_pending또는closed이면 Organization role이나 cached allow와 무관하게 productive command를 거부합니다. - Workspace 관리·catalog command는
WorkspaceCommandContext를 사용하며 active OrganizationMembership과 target WorkspaceMembership을 요구합니다. - Project command는
ProjectCommandContext, Artifact command는ArtifactCommandContext를 사용하며 active OrganizationMembership, target WorkspaceMembership과 각 variant의 exact access version을 요구합니다. - Workspace와 Project/Artifact custody는 active Organization과 정확히 일치해야 합니다.
- link-derived session, Publication viewer session과 unmanaged local file은 아래의 별도 context를 사용하며 일반 membership으로 가장하지 않습니다.
- client가 보낸 role이나 capability claim을 그대로 신뢰하지 않습니다. server/host authority가 현재 membership, policy, grant와 epoch을 다시 읽습니다.
- 마지막으로 사용한 Organization/Workspace는 account-scoped App State에 편의값으로 저장할 수 있지만 artifact, URL, SDK source와 MCP target의 권한 원본이 아닙니다.
- Organization context 전환은 principal identity, Project custody와 artifact identity를 변경하지 않습니다.
- 한 command가 두 Organization의 resource를 동시에 변경하지 않습니다. cross-Organization copy/import는 source read Plan과 destination create Plan을 명시적으로 분리하고 새 destination identity를 만듭니다.
V1 application/domain 경계는 provider-neutral입니다. composition이 인증을 끝낸 뒤 하나의 AuthenticatedCommandContext를 command에 주입하며 domain은 OIDC issuer, JIT 또는 SCIM payload를 받거나 선택하지 않습니다. OIDC + invitation/JIT와 OIDC + SCIM authoritative provisioning, provider-specific ExternalIdentityBinding collision/re-issue rule은 실제 server 배포를 여는 후속 deployment profile이 소유합니다. 이 이연은 V1 command authorization을 미정으로 두는 것이 아니라, 현재의 authenticated context injection을 V1 경계로 고정하고 provider 선택만 후속 profile로 미룹니다.
OrganizationMembership
OrganizationMembership은 principal이 Organization의 관리 plane에 참여할 자격을 나타냅니다. Project/Artifact content access를 부여하지 않습니다.
{
"organizationMembershipRef": "<opaque membership ref>",
"organizationRef": "<organization ref>",
"principalRef": "<principal ref>",
"membershipKind": "member",
"grantedCapabilities": [
"organization.read"
],
"rolePreset": {
"key": "member",
"version": 1
},
"stewardship": "none",
"sponsorPrincipalRef": null,
"state": "active",
"managementSource": {
"kind": "bootstrap",
"sourceRef": null,
"sourceGeneration": null
},
"validFrom": "2026-07-21T00:00:00Z",
"expiresAt": null,
"generation": 1,
"createdByPrincipalRef": "<actor principal ref>",
"createdAt": "2026-07-21T00:00:00Z",
"updatedByPrincipalRef": "<actor principal ref>",
"updatedAt": "2026-07-21T00:00:00Z",
"suspendedByPrincipalRef": null,
"suspendedAt": null,
"suspensionReasonCode": null,
"revokedByPrincipalRef": null,
"revokedAt": null,
"revocationReasonCode": null
}| field | required | 규칙 |
|---|---|---|
organizationMembershipRef | 예 | opaque internal ref |
organizationRef | 예 | 정확히 하나의 Organization |
principalRef | 예 | 정확히 하나의 principal |
membershipKind | 예 | member 또는 guest |
grantedCapabilities[] | 예 | organization capability만 포함하는 정렬·중복 없는 closed set |
rolePreset | 아니요 | {key, version}. 생성 provenance와 UI 표시이며 판정은 grantedCapabilities 사용 |
stewardship | 예 | none 또는 organization_owner |
sponsorPrincipalRef | guest는 예 | guest를 책임지는 같은 Organization의 active member principal. 일반 member는 null |
state | 예 | active, suspended 또는 revoked. expired는 요청 시각에 계산 |
managementSource | 예 | {kind, sourceRef, sourceGeneration}. kind는 bootstrap, manual, invitation, jit 또는 scim |
validFrom | 예 | 이 시각 전에는 inactive |
expiresAt | guest는 예 | member는 nullable, guest는 반드시 미래의 유한 시각 |
generation | 예 | membership 변경용 단조 증가 정수 |
createdByPrincipalRef, createdAt | 예 | 생성 actor/time |
updatedByPrincipalRef, updatedAt | 예 | 마지막 변경 actor/time; 생성 시 created 값과 같음 |
suspendedByPrincipalRef, suspendedAt, suspensionReasonCode | suspended일 때 예 | active/revoked일 때 모두 null |
revokedByPrincipalRef, revokedAt, revocationReasonCode | revoked일 때 예 | active일 때 모두 null |
pending invitation을 active membership row로 만들지 않습니다. 초대 수락 transaction이 principal을 resolve한 뒤 membership을 생성합니다.
membership lifecycle은 active ⇄ suspended → revoked입니다. suspended는 이 Organization에서만 접근을 잠시 멈추는 가역 상태이고 revoked는 같은 row의 terminal 상태입니다. expiresAt 도달은 저장 mutation을 기다리지 않고 요청 시 expired로 계산합니다. 같은 (organizationRef, principalRef)에는 non-revoked membership이 최대 하나만 존재하며 재초대·재가입은 기존 row와의 관계를 명시적으로 정리한 뒤 새 generation을 만듭니다.
managementSource.sourceRef는 invitation 또는 directory object 같은 관리 원본을 추적할 때만 사용합니다. provider ID를 principal identity로 승격하지 않으며 source가 관리하는 membership은 People 화면에서 read-only 원본과 마지막 generation을 표시합니다.
Organization role preset
Organization role key는 다음 여덟 개뿐입니다.
| preset | exact capability set |
|---|---|
owner | closed organization capability catalog 전체 |
admin | organization.read, organization.settings.manage, organization.members.read, organization.members.manage, organization.workspaces.manage, organization.policy.read, organization.policy.manage, organization.security.read, organization.sessions.read, organization.devices.read, organization.audit.read, organization.templates.read, organization.templates.publish, organization.templates.acquire, organization.templates.allocate, organization.templates.publication.approve, organization.templates.acquisition.approve, organization.templates.use.approve |
security_admin | organization.read, organization.members.read, organization.policy.read, organization.security.read, organization.security.manage, organization.sessions.read, organization.sessions.manage, organization.devices.read, organization.devices.manage, organization.audit.read, organization.audit.export, organization.legal_hold.manage |
billing_admin | organization.read, organization.license.read, organization.license.manage, organization.usage.read, organization.templates.read, organization.templates.purchase, organization.templates.acquisition.approve |
template_manager | organization.read, organization.templates.read, organization.templates.publish, organization.templates.acquire, organization.templates.allocate |
template_approver | organization.read, organization.templates.read, organization.templates.use.approve |
auditor | organization.read, organization.policy.read, organization.audit.read, organization.audit.export |
member | organization.read |
Closed organization capability catalog:
organization.read
organization.settings.manage
organization.members.read
organization.members.manage
organization.workspaces.manage
organization.policy.read
organization.policy.manage
organization.security.read
organization.security.manage
organization.license.read
organization.license.manage
organization.usage.read
organization.sessions.read
organization.sessions.manage
organization.devices.read
organization.devices.manage
organization.audit.read
organization.audit.export
organization.legal_hold.manage
organization.ownership.transfer
organization.templates.read
organization.templates.publish
organization.templates.acquire
organization.templates.purchase
organization.templates.allocate
organization.templates.publication.approve
organization.templates.acquisition.approve
organization.templates.use.approveOrganization role은 Workspace membership, Project role 또는 artifact access를 내포하지 않습니다. Organization owner, admin, security admin과 auditor도 별도 WorkspaceMembership과 AccessGrant가 없으면 Project 이름, thumbnail, content, Result와 source를 읽을 수 없습니다.
조직 역할 위임은 closed delegation matrix를 사용합니다. 기본 matrix에서 Owner는 모든 조직 역할을 지정할 수 있고 Admin은 admin, template_manager, template_approver와 member만 지정할 수 있습니다. Security Admin, Billing Admin, Template Manager, Template Approver와 Auditor는 조직 역할을 지정하지 못합니다. owner 지정·해제는 항상 organization.ownership.transfer를 사용하며 membership change로 우회하지 않습니다. actor가 보유하지 않은 관리 capability, policy ceiling을 넘는 capability array 또는 client가 조립한 custom role을 저장하지 않습니다.
Organization-scoped session과 device control
로그인 session과 device identity는 account control plane이 소유합니다. 한 Organization의 관리자가 다른 Organization까지 사용자의 로그인을 종료하지 않도록 조직 관리 동작은 다음 scope-bound control을 사용합니다.
{
"organizationSessionControlRef": "<opaque control ref>",
"organizationRef": "<organization ref>",
"principalRef": "<principal ref>",
"authenticationSessionRef": "<authentication session ref>",
"state": "active",
"generation": 1
}{
"organizationDeviceAdmissionRef": "<opaque admission ref>",
"organizationRef": "<organization ref>",
"principalRef": "<principal ref>",
"deviceRef": "<device ref>",
"state": "allowed",
"generation": 1
}- session control state는
active또는revoked, device admission state는allowed또는blocked입니다. organization.sessions.manage는 exact session 하나 또는 해당 principal의 이 Organization session 전체를 revoke할 수 있습니다.organization.devices.manage는 이 Organization에서 device 사용을 차단할 수 있지만 기기를 원격 삭제하거나 다른 Organization의 device admission을 바꾸지 않습니다.- revoke/block은 Organization
authorizationEpoch과 해당 control generation을 같은 transaction에서 증가시키고 cached decision, object ticket, Run, WebSocket과 MCP session을 재검증합니다. - 전역 계정 session 종료, 비밀번호·MFA 변경과
PrincipalSecurityState변경은 Organization 관리 capability가 아니라 account/identity authority를 사용합니다. - offline device는 이미 발급된 signed lease의 최대 노출 시간을 넘겨 허용하지 않으며 조직 화면은 마지막 확인 시각과 아직 회수되지 않은 offline window를 숨기지 않습니다.
WorkspaceMembership
WorkspaceMembership은 principal이 한 Workspace의 관리·catalog plane에 참여할 자격을 나타냅니다. OrganizationMembership과 별도 row이며 Project/Artifact content access를 자동 부여하지 않습니다.
{
"workspaceMembershipRef": "<opaque membership ref>",
"organizationRef": "<organization ref>",
"workspaceRef": "<workspace ref>",
"principalRef": "<principal ref>",
"grantedCapabilities": [
"workspace.read"
],
"rolePreset": {
"key": "member",
"version": 1
},
"stewardship": "none",
"state": "active",
"managementSource": {
"kind": "bootstrap",
"sourceRef": null,
"sourceGeneration": null
},
"validFrom": "2026-07-21T00:00:00Z",
"expiresAt": null,
"generation": 1,
"createdByPrincipalRef": "<actor principal ref>",
"createdAt": "2026-07-21T00:00:00Z",
"updatedByPrincipalRef": "<actor principal ref>",
"updatedAt": "2026-07-21T00:00:00Z",
"suspendedByPrincipalRef": null,
"suspendedAt": null,
"suspensionReasonCode": null,
"revokedByPrincipalRef": null,
"revokedAt": null,
"revocationReasonCode": null
}WorkspaceMembership은 같은 principal의 active OrganizationMembership을 요구합니다. OrganizationMembership이 revoked/expired/suspended이면 하위 WorkspaceMembership과 AccessGrant가 active여도 사용할 수 없습니다.
WorkspaceMembership의 state, managementSource, validFrom, expiresAt, generation, created/updated/suspended/revoked actor·time·reason field는 OrganizationMembership과 같은 규칙을 사용합니다. 같은 (workspaceRef, principalRef)에는 non-revoked membership이 최대 하나만 존재합니다. guest의 WorkspaceMembership expiresAt은 parent guest OrganizationMembership expiry보다 늦을 수 없습니다.
Workspace role preset은 다음 세 개뿐입니다.
| preset | exact capability set |
|---|---|
owner | closed workspace capability catalog 전체 |
admin | workspace.read, workspace.settings.manage, workspace.members.read, workspace.members.manage, workspace.policy.read, workspace.policy.manage, workspace.projects.create, workspace.archive |
member | workspace.read, workspace.projects.create |
Closed workspace capability catalog:
workspace.read
workspace.settings.manage
workspace.members.read
workspace.members.manage
workspace.policy.read
workspace.policy.manage
workspace.projects.create
workspace.archive
workspace.ownership.transferworkspace.read는 Workspace 이름과 자신의 접근 상태를 보는 최소 capability입니다. 다른 Project의 존재, 이름과 thumbnail을 목록에 포함시키는 근거가 아닙니다. Project Catalog는 각 Project의 effective AccessGrant를 추가로 확인합니다.
Project Catalog visibility는 하나의 AccessGrant 경로를 사용한다
Project Catalog가 Project row를 반환하는 유일한 access source는 다음 값입니다.
effective AccessGrant모든 visible Project는 requesting principal의 active OrganizationMembership과 target WorkspaceMembership을 통과하고, 그 Project에 적용되는 effective AccessGrant를 가져야 합니다. Organization 또는 Workspace membership만으로 Project를 보이게 하거나, creator provenance를 access source로 사용하지 않습니다. grant가 없거나 revoked·expired이거나, grantee·Organization· Workspace·Project scope가 일치하지 않거나, grant를 확정적으로 평가할 수 없으면 row 자체를 반환하지 않습니다.
이 단일 authorization path는 audit과 revoke를 단순하게 유지하고, membership 변경 하나가 Workspace의 Project를 대량으로 조용히 노출하는 일을 막습니다. Project Catalog query도 membership path와 grant path를 union하지 않고 하나의 grant index로 후보를 제한할 수 있습니다. 따라서 principal당 grant index fan-out이 catalog query의 주된 scaling cost이고, membership 규모와 Project 규모를 곱한 두 경로 union은 만들지 않습니다.
Organization은 유일한 tenant identity입니다. Project visibility와 content authorization의 authority는 Organization custody, memberships와 AccessGrant뿐입니다. 별도 tenant concept, object ACL, ACL blob과 allowedUsers[]를 두 번째 authority로 추가하지 않습니다.
Project visibility authorization authority set:
Organization custody
memberships
AccessGrantProject role과 AccessGrant
Project role은 다섯 개다
Project role key는 다음 closed enum입니다.
owneradmineditorcommenterviewer
Project role은 AccessGrant를 만들 때 canonical capability set을 선택하기 위한 preset입니다. 저장된 grantedCapabilities[]가 권한 원본이며 preset label을 별도 권한으로 다시 평가하지 않습니다.
| preset | exact capability set |
|---|---|
viewer | artifact.read, result.read, evidence.read, comment.read |
commenter | Viewer + comment.write, comment.resolve |
editor | Commenter + artifact.write, artifact.create, artifact.copy, artifact.export, run.execute, run.cancel, review.request |
admin | Editor + artifact.fork, source.download, publication.create, publication.update_policy, publication.withdraw, template.release.prepare, template.release.candidate.manage, share.manage, members.manage |
owner | Admin + project.delete, ownership.transfer |
Closed Project/Artifact capability catalog:
artifact.read
artifact.write
artifact.create
artifact.copy
artifact.fork
artifact.export
source.download
output.download
result.read
evidence.read
comment.read
comment.write
comment.resolve
review.request
run.execute
run.cancel
publication.create
publication.update_policy
publication.withdraw
template.release.prepare
template.release.candidate.manage
share.manage
members.manage
project.delete
ownership.transferOrganization/Workspace capability와 Project/Artifact capability를 한 array에 섞지 않습니다.
template.release.prepare는 source revision에서 immutable release payload candidate를 준비하는 Project-scoped 권한입니다. template.release.candidate.manage는 source Project 안의 candidate 취소·만료 기록을 관리하며 Marketplace listing authority가 아닙니다. 고정된 release의 publisher lifecycle, listing과 offer는 publisher가 개인이면 exact owner principal, 조직이면 organization.templates.publish만 사용하는 별도 control-plane command입니다. 이 command가 Project capability를 동시에 요청하지 않습니다. security/legal yank와 Marketplace moderation은 별도 platform-scoped authority이며 Project Admin 또는 Organization Admin 권한으로 우회하지 않습니다.
Project role에는 범위 없는 template.instantiate를 두지 않습니다. 템플릿으로 새 작업을 만드는 권한은 개인 grant 또는 Organization Library source right·allocation·approval branch와 새 Project용 workspace.projects.create 또는 기존 Project용 Project-scoped artifact.create를 함께 확인합니다. 구매·배정만으로 source Project 또는 destination content 권한을 만들지 않습니다. exact 분기는 Template Marketplace와 Organization Library를 따릅니다.
output.download는 어떤 Project role preset에도 자동 포함되지 않는 explicit capability입니다. Project에 저장된 PDF 또는 외부 공유 PDF를 파일로 내려받을 때 별도 AccessGrant와 effective output policy가 함께 허용해야 합니다. 화면에서 Project PDF를 읽는 것은 PDF artifact의 artifact.read로 판정합니다. output.download, artifact.read, artifact.export, source.download와 printAllowed는 서로 대체하지 않습니다.
Reviewer는 assignment다
Reviewer라는 Project role은 존재하지 않습니다. review.decide는 Project/Artifact capability catalog와 AccessGrant에서 금지되며 다음 조건을 모두 만족하는 exact ReviewRequestAssignment에서만 파생됩니다.
- principal에게 active OrganizationMembership과 WorkspaceMembership이 있습니다.
- principal이 ReviewRequest의 active assignee입니다.
- ReviewRequest가 exact immutable revision과 review policy를 pin합니다.
- assignment와 ReviewRequest가 expired, revoked, superseded 또는 completed가 아닙니다.
- Organization/WorkspacePolicy의
reviewAccess.assignedDecision이 모두allowed이고 pinned review policy snapshot과 일치합니다.
Review assignment는 다른 artifact 읽기, current head 편집, Run, Share와 Project 관리 권한을 만들지 않습니다. 필요한 revision 읽기 capability는 ReviewRequest 생성 transaction이 별도 scoped access로 계획하고 부여합니다.
{
"reviewRequestAssignmentRef": "<opaque assignment ref>",
"reviewRequestRef": "<opaque review request ref>",
"organizationRef": "<organization ref>",
"workspaceRef": "<workspace ref>",
"projectRef": "<project ref>",
"artifactRef": "<artifact ref>",
"revisionRef": "<immutable revision ref>",
"reviewPolicySnapshotRef": "<immutable review policy snapshot ref>",
"reviewPolicyRevision": 3,
"reviewPolicyDigest": "sha256:<digest>",
"assigneePrincipalRef": "<human principal ref>",
"grantedCapabilities": [
"artifact.read",
"result.read",
"evidence.read",
"review.decide"
],
"state": "active",
"validFrom": "2026-07-21T00:00:00Z",
"expiresAt": "2026-07-28T00:00:00Z",
"generation": 1
}reviewRequestAssignmentRef는 assignee마다 별도로 발급하고 (reviewRequestRef, assigneePrincipalRef) active uniqueness를 강제합니다. capability set은 위 네 값의 closed set이고 exact revisionRef, reviewPolicySnapshotRef, revision과 digest가 모두 일치할 때만 유효합니다. review.decide는 다른 revision, current head, Project 또는 sibling artifact로 상속되지 않습니다. assignment state는 active, completed, revoked, superseded 중 하나이며 expiry는 요청 시 계산합니다. 생성/변경은 ReviewRequest와 assignment, pinned revision read access, audit와 outbox를 한 transaction으로 처리합니다.
AccessGrant schema
{
"grantRef": "<opaque grant ref>",
"organizationRef": "<organization ref>",
"workspaceRef": "<workspace ref>",
"resource": {
"kind": "project",
"projectRef": "<opaque project ref>"
},
"grantee": {
"kind": "principal",
"principalRef": "<opaque principal ref>"
},
"grantedCapabilities": [
"artifact.read",
"result.read",
"evidence.read",
"comment.read"
],
"rolePreset": {
"key": "viewer",
"version": 1
},
"stewardship": "none",
"inheritToArtifacts": true,
"state": "active",
"validFrom": "2026-07-21T00:00:00Z",
"expiresAt": null,
"generation": 1,
"organizationPolicyRevision": 7,
"workspacePolicyRevision": 4,
"createdBy": "<actor principal ref>",
"createdAt": "2026-07-21T00:00:00Z",
"updatedBy": null,
"updatedAt": null,
"revokedBy": null,
"revokedAt": null,
"revocationReason": null
}| field | required | 규칙 |
|---|---|---|
grantRef | 예 | opaque internal ref. 사용자·LLM target이 아님 |
| Organization/Workspace scope | 예 | resource custody와 정확히 일치해야 함 |
resource | 예 | project 또는 artifact closed union |
grantee | 예 | principal, group, linkSession closed union |
grantedCapabilities[] | 예 | Project/Artifact capability만 포함하는 정렬·중복 없는 closed set |
rolePreset | 아니요 | {key, version} 생성 provenance. capability authority가 아님 |
stewardship | 예 | none 또는 project_owner; direct principal Project grant에만 project_owner 허용 |
inheritToArtifacts | 예 | Project resource에서만 true 가능. Artifact grant는 false |
state | 예 | active 또는 revoked; expiry는 요청 시 계산 |
validFrom, expiresAt | 예/nullable | request-time eligibility에 사용 |
generation | 예 | grant 변경용 concurrency token |
organizationPolicyRevision | 예 | 생성/변경 시 검토한 OrganizationPolicy revision |
workspacePolicyRevision | 예 | 생성/변경 시 검토한 WorkspacePolicy revision |
createdBy, createdAt, updatedBy, updatedAt, revokedBy, revokedAt, revocationReason | 예/nullable | 일곱 member는 항상 존재하고 아직 적용되지 않은 값은 null |
AccessGrant lifecycle member set:
createdBy
createdAt
updatedBy
updatedAt
revokedBy
revokedAt
revocationReason일곱 lifecycle member는 persisted AccessGrant에 항상 존재합니다. createdBy와 createdAt은 생성 시점부터 non-null입니다. 생성 후 변경이 없으면 updatedBy와 updatedAt은 둘 다 null이고, 변경이 있으면 둘 다 non-null입니다. state=active이면 revokedBy, revokedAt, revocationReason은 모두 null이고, state=revoked이면 세 값이 모두 non-null입니다. member 누락, 부분적으로만 채운 pair/triple, active grant의 revoke metadata와 revoked grant의 null revoke metadata는 저장·직렬화·digest 계산 전에 거부합니다.
이 exact always-present shape는 audit reader가 grant record 하나만으로 누가 접근을 제거했고 왜 제거했는지 답하게 합니다. 일곱 field의 고정 row/wire 비용은 발생하지만 optional member shape를 분기하지 않으므로 serializer, index projection과 canonical digest가 결정적이고 schema-version별 shape fan-out이 늘지 않습니다.
AccessGrant에 explicit deny를 넣지 않습니다. applicable active grant의 capability는 union한 뒤 policy ceiling과 operation constraints로 제한합니다. review.decide처럼 assignment에서만 파생되는 capability는 AccessGrant validation에서 거부합니다. allowedUsers[], ACL JSON blob, owner_email, shared, public boolean과 raw link secret을 AccessGrant로 사용하지 않습니다.
resource와 grantee는 다음 variant만 허용합니다.
resource.project = { kind: "project", projectRef }
resource.artifact = { kind: "artifact", projectRef, artifactRef }
grantee.principal = { kind: "principal", principalRef }
grantee.group = { kind: "group", groupRef }
grantee.linkSession = { kind: "linkSession", linkSessionRef }다른 variant, email, role name, public slug와 raw token은 거부합니다. AccessGrant lifecycle의 state 조건은 OrganizationMembership의 revoke 규칙을 따르되 persisted member 이름과 requiredness는 위 일곱-member always-present 계약이 소유합니다.
Project grant 상속은 같은 Project의 child artifact로만 향합니다. Artifact grant가 parent Project나 sibling artifact의 존재·metadata를 노출하지 않습니다.
Guest, Group과 linkSession
Authenticated guest
Guest는 외부 identity provider로 로그인하고 내부 human principal로 resolve된 사용자입니다.
- OrganizationMembership의
membershipKind는guest입니다. sponsorPrincipalRef와 유한한expiresAt이 필수입니다.- sponsor는 active member이며
organization.members.manage를 가져야 합니다. - sponsor가 suspended이면 sponsored guest도 request 시 즉시 inactive이며 sponsor가 다시 active가 될 때까지 접근하지 못합니다. sponsor가 revoke/expire되거나
organization.members.manage를 잃으면 sponsored guest membership을 같은 transaction에서 revoke하고 Organization epoch을 증가시킵니다. 새 sponsor로 변경하려면 guest 변경 Plan/Apply를 먼저 완료합니다. - guest expiry는 WorkspaceMembership, GroupMembership과 AccessGrant expiry의 상한입니다.
- guest는
organization_owner,workspace_owner또는project_ownerstewardship을 가질 수 없습니다. - guest에게 허용되는 최대 Project role preset은
editor입니다. Admin/Owner capability는 hard safety ceiling으로 제거합니다. - guest의 Organization capability ceiling은
organization.read, Workspace capability ceiling은workspace.read입니다. guest에게 member 관리, policy, audit, license, device, Workspace 생성과 ownership capability를 부여할 수 없습니다. - guest의 Project capability ceiling은
artifact.read,artifact.write,output.download,result.read,evidence.read,comment.read,comment.write,comment.resolve,review.request,run.execute,run.cancel입니다.output.download는 explicit grant와 output policy가 모두 허용할 때만 남습니다. copy/fork/export/source download, publication, template release, catalog listing, Share/member 관리, delete와 ownership은 grant에 있어도 제거합니다. Organization template allocation도 guest를 기본 제외하며 명시적 principal allocation과 유한 expiry 없이는 템플릿 사용 권리를 만들지 않습니다. - Commenter 또는 Editor 외부 접근은 authenticated guest만 사용합니다. anonymous link를 Commenter/Editor처럼 승격하지 않습니다.
- guest membership이 revoke/expire되면 하위 membership, group-derived grant, ticket와 session을 즉시 거부합니다.
pending guest invitation은 membership이 아닙니다. accept 전에는 resource 목록, 이름, thumbnail과 dependency 존재를 노출하지 않습니다.
Group
Group은 한 Organization 안의 principal 집합이며 AccessGrant grantee가 될 수 있습니다.
{
"groupRef": "<opaque group ref>",
"organizationRef": "<organization ref>",
"groupKey": "structural_reviewers",
"displayName": "Structural reviewers",
"state": "active",
"generation": 3
}groupKey는 Organization 안에서 고유한 semantic key입니다.- Group member는 direct principal만 허용합니다. nested group은 금지합니다.
- member principal은 같은 Organization의 active member/guest여야 합니다.
- Group 자체는 OrganizationMembership이나 WorkspaceMembership을 갖지 않습니다.
- group-derived AccessGrant를 사용할 때 principal 자신의 active OrganizationMembership과 target WorkspaceMembership을 다시 요구합니다.
- guest를 Group에 넣을 수 있지만 guest expiry와 policy ceiling이 우선합니다.
- Group은 organization/workspace/project owner stewardship을 가질 수 없습니다.
- group membership 변경은 Organization
authorizationEpoch을 증가시킵니다.
Group membership은 다음 closed object입니다.
{
"groupMembershipRef": "<opaque group membership ref>",
"organizationRef": "<organization ref>",
"groupRef": "<group ref>",
"principalRef": "<principal ref>",
"state": "active",
"managementSource": {
"kind": "manual",
"sourceRef": null,
"sourceGeneration": null
},
"validFrom": "2026-07-21T00:00:00Z",
"expiresAt": null,
"generation": 1,
"createdByPrincipalRef": "<actor principal ref>",
"createdAt": "2026-07-21T00:00:00Z",
"updatedByPrincipalRef": "<actor principal ref>",
"updatedAt": "2026-07-21T00:00:00Z",
"suspendedByPrincipalRef": null,
"suspendedAt": null,
"suspensionReasonCode": null,
"revokedByPrincipalRef": null,
"revokedAt": null,
"revocationReasonCode": null
}state/expiry, managementSource와 created/updated/suspended/revoked actor·time·reason은 OrganizationMembership과 같은 규칙입니다. 같은 (groupRef, principalRef)에는 non-revoked GroupMembership이 최대 하나만 존재합니다. guest GroupMembership expiry는 guest OrganizationMembership expiry보다 늦을 수 없습니다.
linkSession
linkSession은 ShareLink의 raw secret을 검증한 뒤 발급하는 target-bound, expiring session입니다. principal이나 guest membership으로 가장하지 않습니다.
{
"linkSessionRef": "<opaque session ref>",
"shareLinkRef": "<opaque source ShareLink ref>",
"organizationRef": "<organization ref>",
"workspaceRef": "<workspace ref>",
"resource": {
"kind": "artifact",
"projectRef": "<project ref>",
"artifactRef": "<artifact ref>"
},
"capabilityCeiling": [
"artifact.read",
"result.read",
"evidence.read"
],
"state": "active",
"expiresAt": "2026-07-28T00:00:00Z",
"generation": 2,
"createdAt": "2026-07-21T00:00:00Z"
}정확한 규칙:
- anonymous/external link는 OrganizationPolicy와 WorkspacePolicy가 모두 허용할 때만 생성합니다.
- expiry는 필수이며 Organization/Workspace 최대값보다 길 수 없습니다.
resource는 AccessGrant와 같은project또는artifactclosed union이며 Organization/Workspace custody와 일치해야 합니다. Project target은 같은 Project의 명시적으로 허용된 child closure에만 적용됩니다.- linkSession capability는
artifact.read,result.read,evidence.read,output.download의 subset만 허용합니다. 새 link의 기본 ceiling에는output.download를 넣지 않으며, explicit AccessGrant와 effective output policy가 모두 허용할 때만 추가합니다. artifact.write, comment, review, Run, export, source download, Share, member 관리와 ownership capability를 넣을 수 없습니다.- 한 resource와 허용된 child closure 밖으로 상속하지 않습니다.
- raw ShareLink secret과 password는 저장·로그·audit·URL analytics에 남기지 않습니다. verifier digest만 저장합니다.
- optional password는 expiry, revocation, target binding과 Viewer-only ceiling을 대체하지 않습니다.
- revoke와 rotate는 linkSession generation을, policy 축소는 owning Organization/Workspace epoch을 바꾸며 열린 HTTP/WebSocket session도 재검증합니다. expiry는 저장 mutation을 기다리지 않고 요청 시각으로 즉시 거부합니다.
raw ShareLink secret을 검증한 이후의 protected request는 다음 별도 context를 사용합니다.
LinkProjectCommandContext = {
kind: "link_project",
linkSessionRef,
shareLinkRef,
organizationRef,
workspaceRef,
resource: { kind: "project", projectRef },
organizationAuthorizationEpoch,
workspaceAuthorizationEpoch,
projectAccessGeneration,
linkSessionGeneration
}
LinkArtifactCommandContext = {
kind: "link_artifact",
linkSessionRef,
shareLinkRef,
organizationRef,
workspaceRef,
resource: { kind: "artifact", projectRef, artifactRef },
organizationAuthorizationEpoch,
workspaceAuthorizationEpoch,
projectAccessGeneration,
artifactAccessGeneration,
linkSessionGeneration
}두 Link context에는 principalRef, OrganizationMembership, WorkspaceMembership과 일반 authentication session을 요구하지 않습니다. Project/Artifact variant를 섞을 수 없고 exact target, active linkSession, matching linkSession AccessGrant, expiry, 모든 pinned epoch/generation과 output policy를 매 요청마다 검증합니다. raw ShareLink secret은 최초 session 교환 이후 command context로 전달하지 않습니다.
Publication viewer session
Public/unlisted Report Publication은 Project/Artifact AccessGrant나 ShareLink linkSession으로 가장하지 않습니다. Publication policy를 통과한 viewer에는 exact immutable Publication Version과 PDF rendition에 묶인 다음 session을 발급합니다.
{
"publicationViewerSessionRef": "<opaque session ref>",
"organizationRef": "<organization ref>",
"workspaceRef": "<workspace ref>",
"publicationRef": "<publication ref>",
"publicationVersionRef": "<immutable publication version ref>",
"source": {
"projectRef": "<project ref>",
"artifactRef": "<source Report ref>",
"revisionRef": "<immutable Report revision ref>"
},
"pdfRenditionObjectRef": "<opaque immutable rendition object ref>",
"pdfRenditionDigest": "sha256:<digest>",
"audience": "unlisted",
"capabilityCeiling": [
"artifact.read",
"result.read",
"evidence.read"
],
"outputPolicySnapshotRef": "<immutable output policy snapshot ref>",
"state": "active",
"sessionGeneration": 1,
"publicationGeneration": 5,
"organizationAuthorizationEpoch": 31,
"workspaceAuthorizationEpoch": 18,
"expiresAt": "2026-07-22T00:00:00Z"
}audience는public또는unlisted입니다.workspaceaudience는 authenticated principal의 normal Organization/Workspace membership과 source/Publication access를 사용합니다.- capability ceiling은
artifact.read,result.read,evidence.read,output.download의 subset입니다.output.download는 Publication Version의 pinned delivery policy와 effective Organization/Workspace output policy가 명시적으로 허용할 때만 포함합니다. - session은 exact Publication Version,
pdfRenditionObjectRef와pdfRenditionDigest밖의 Project/Artifact를 탐색하거나 읽는 권한이 아닙니다. write, Run, comment, review, export, source download, Share 관리와 ownership capability는 금지합니다. state는active또는revoked이며sessionGeneration은 rotate/revoke 시 증가합니다.- protected viewer/download request는 session ref/generation, publication/version,
pdfRenditionObjectRef와pdfRenditionDigest, publication generation, Organization/Workspace epoch, expiry와 current suspend/withdraw 상태를 모두 검증합니다. - withdraw, suspend, policy 축소 또는 Publication generation 변경은 열린 viewer/download session을 재검증합니다. 이미 내려받은 PDF는 회수할 수 없습니다.
따라서 Project PDF download는 PDF Artifact AccessGrant의 output.download, ShareLink download는 linkSession AccessGrant의 output.download, Publication PDF download는 PublicationViewerSession의 explicit output.download를 사용합니다. 세 경로를 하나의 public boolean이나 범위 없는 role로 합치지 않습니다.
EntitlementLease 경계
EntitlementLease는 account/license가 제품 기능을 사용할 수 있는지 증명하고 Project/Artifact access를 부여하지 않습니다.
허용되는 scope:
principalRef- optional exact
organizationRef가 붙은 product capability/limit snapshot - device binding, issued/renew/expiry와 security epoch
- product capability key, quantitative limit와 offline policy
금지되는 field와 의미:
- 범위 없는
roles[] projectRole,workspaceRole또는 Project/Workspace membership 사본- AccessGrant, Group membership, guest admission과 linkSession
- artifact/project list 또는 current head
- email, file path, ACL와 ShareLink secret
Local offline authorization이 필요하면 local authority가 마지막으로 검증된 membership/grant/policy snapshot을 account-scoped encrypted App State에 별도로 저장합니다. 이 snapshot은 lease의 roles[]로 압축하지 않고 scope, generation, epoch과 expiry를 보존합니다.
OrganizationPolicy와 WorkspacePolicy
Immutable revision과 active pointer
OrganizationPolicy와 WorkspacePolicy는 현재 row를 덮어쓰는 mutable 설정 blob이 아닙니다. 각 변경은 새 immutable revision을 만들고, owning Organization 또는 Workspace의 policyRevision active pointer를 CAS로 전환합니다.
- revision의 body, canonical digest, predecessor, 생성 actor와 생성 시각은 생성 뒤 바꾸지 않습니다.
- active pointer 변경, owning aggregate
generation, 해당 authorization epoch, audit와 outbox를 한 transaction에서 갱신합니다. - Plan은 현재 active revision과 owning aggregate generation을 pin하며 Apply 전에 하나라도 바뀌면 전체 거부합니다.
- rollback은 과거 immutable revision을 수정하는 동작이 아니라 호환되는 새 activation으로 이전 revision을 다시 가리키는 관리 변경입니다.
- policy revision ref, provider resource와 Organization/Workspace ref를 portable artifact에 넣지 않습니다.
Policy revision object
OrganizationPolicy는 다음 closed shape를 사용합니다.
{
"organizationPolicyRevisionRef": "<opaque immutable policy revision ref>",
"organizationRef": "<organization ref>",
"policyRevision": 7,
"schemaVersion": 1,
"predecessorRevisionRef": "<previous policy revision ref>",
"digest": "sha256:<canonical policy digest>",
"capabilityCeilings": {
"organization": ["<organization capabilities>"],
"workspace": ["<workspace capabilities>"],
"project": ["<project/artifact capabilities>"]
},
"externalAccess": {
"authenticatedGuests": "disabled",
"maximumGuestLifetimeSeconds": 7776000,
"anonymousViewerLinks": "disabled",
"maximumLinkLifetimeSeconds": 604800,
"linkPassword": "optional"
},
"dataPolicy": {
"sourceDownload": "allowed",
"export": "allowed",
"residencyClass": "home_region",
"retentionPolicyRef": "<versioned retention policy ref>"
},
"reportOutput": {
"pdfGeneration": "allowed",
"projectPdfSave": "allowed",
"selectedFileExport": "allowed",
"printAllowed": true,
"sharedPdfDownload": "explicit_capability",
"classification": "internal",
"watermarkMode": "external_only"
},
"reviewAccess": {
"assignedDecision": "allowed"
},
"templateDistribution": {
"publicMarketplacePublishing": "approval_required",
"freeOrganizationAcquisition": "allowed",
"paidOrganizationPurchase": "approval_required",
"defaultPublicationApproverGroupRef": null,
"defaultAcquisitionApproverGroupRef": null,
"organizationAllocation": "everyone_allowed",
"guestAllocation": "explicit_only",
"minimumUseApproval": "immediate",
"defaultUseApproverGroupRef": null,
"offlineUse": "allowed",
"maximumOfflineUseSeconds": 604800
},
"createdByPrincipalRef": "<actor principal ref>",
"createdAt": "2026-07-21T00:00:00Z"
}Closed enum:
- revision ref는 opaque immutable ref이고
predecessorRevisionRef는 첫 revision에서만 null입니다. schemaVersion은 양의 정수이며digest는 metadata를 제외한 canonical policy body의 SHA-256입니다.authenticatedGuests:disabled또는allowedanonymousViewerLinks:disabled또는allowedlinkPassword:disabled,optional,requiredsourceDownload,export:allowed또는deniedresidencyClass:local_only,home_region,approved_regionspdfGeneration,projectPdfSave,selectedFileExport:allowed또는deniedprintAllowed: booleansharedPdfDownload:disabled또는explicit_capabilityclassification:public,internal,confidential,restrictedwatermarkMode:none,external_only,all_outputsreviewAccess.assignedDecision:allowed또는deniedtemplateDistribution.publicMarketplacePublishing:disabled,approval_required또는allowedtemplateDistribution.freeOrganizationAcquisition:disabled또는allowedtemplateDistribution.paidOrganizationPurchase:disabled,approval_required또는allowedtemplateDistribution.defaultPublicationApproverGroupRef: 같은 Organization에서organization.templates.publication.approve를 가진 active member가 포함된 group ref 또는 nulltemplateDistribution.defaultAcquisitionApproverGroupRef: 같은 Organization에서organization.templates.acquisition.approve를 가진 active member가 포함된 group ref 또는 nulltemplateDistribution.organizationAllocation:disabled,selected_only또는everyone_allowedtemplateDistribution.guestAllocation:disabled또는explicit_onlytemplateDistribution.minimumUseApproval:immediate또는approval_requiredtemplateDistribution.defaultUseApproverGroupRef: 같은 Organization의 active group ref 또는 nulltemplateDistribution.offlineUse:disabled또는allowed
maximumGuestLifetimeSeconds, maximumLinkLifetimeSeconds와 maximumOfflineUseSeconds는 양의 정수입니다. 외부 access나 offline use가 disabled여도 값은 policy validation과 다음 enable Plan에 사용하므로 누락하지 않습니다.
Template policy는 Marketplace 가격·주문·grant 또는 allocation을 저장하지 않고 조직에서 허용할 수 있는 상한만 고정합니다. publicMarketplacePublishing=approval_required 또는 paidOrganizationPurchase=approval_required인데 목적에 맞는 default approver group이 없으면 해당 Plan은 Apply 불가 상태를 반환합니다. selected_only는 group/principal allocation만 허용하고 everyone을 거부합니다. minimumUseApproval=approval_required이면 Library entry가 immediate를 요청해도 effective policy는 approval-required입니다. 이때 allocation이 approver group을 지정하지 않으면 defaultUseApproverGroupRef를 사용하며, 둘 다 없으면 Plan은 Apply 불가 상태를 반환합니다. 각 approver group에는 purpose별 approval capability를 가진 active member가 최소 한 명 있어야 합니다. requester의 자기 승인은 모든 template approval purpose에서 금지합니다. guest는 guestAllocation=explicit_only일 때도 everyone/group에서 파생되지 않고 exact principal allocation과 유한 expiry가 필요합니다. 정확한 사용 권리와 allocation은 Template Marketplace와 Organization Library를 따릅니다.
WorkspacePolicy는 다음 closed shape를 사용합니다.
{
"workspacePolicyRevisionRef": "<opaque immutable policy revision ref>",
"organizationRef": "<organization ref>",
"workspaceRef": "<workspace ref>",
"policyRevision": 4,
"schemaVersion": 1,
"predecessorRevisionRef": "<previous policy revision ref>",
"digest": "sha256:<canonical policy digest>",
"capabilityCeilings": {
"workspace": ["<workspace capabilities>"],
"project": ["<project/artifact capabilities>"]
},
"externalAccess": {
"authenticatedGuests": "disabled",
"maximumGuestLifetimeSeconds": 2592000,
"anonymousViewerLinks": "disabled",
"maximumLinkLifetimeSeconds": 86400,
"linkPassword": "required"
},
"dataPolicy": {
"sourceDownload": "denied",
"export": "allowed"
},
"reportOutput": {
"pdfGeneration": "allowed",
"projectPdfSave": "allowed",
"selectedFileExport": "allowed",
"printAllowed": false,
"sharedPdfDownload": "disabled",
"classification": "confidential",
"watermarkMode": "all_outputs"
},
"reviewAccess": {
"assignedDecision": "allowed"
},
"createdByPrincipalRef": "<actor principal ref>",
"createdAt": "2026-07-21T00:00:00Z"
}WorkspacePolicy에는 Organization capability ceiling, residency와 retention field를 넣지 않습니다. OrganizationPolicy보다 범위를 넓히거나 lifetime을 늘릴 수 없고, classification을 낮추거나 watermark를 약화할 수 없습니다. reviewAccess는 deny-wins입니다.
effective output policy는 allow boolean에 AND, allowed/denied에 deny-wins, disabled/explicit_capability에 disabled-wins를 적용합니다. classification은 public < internal < confidential < restricted, watermark는 none < external_only < all_outputs 순서에서 더 강한 값을 선택합니다.
PDF render를 시작하기 전에 effective OrganizationPolicy와 WorkspacePolicy를 다음 immutable output policy snapshot으로 resolve합니다.
{
"outputPolicySnapshotRef": "<opaque immutable snapshot ref>",
"organizationRef": "<organization ref>",
"workspaceRef": "<workspace ref>",
"organizationPolicyRevision": 7,
"workspacePolicyRevision": 4,
"pdfGenerationAllowed": true,
"projectPdfSaveAllowed": true,
"selectedFileExportAllowed": true,
"printAllowed": false,
"sharedPdfDownloadMode": "disabled",
"classification": "confidential",
"watermark": {
"mode": "required",
"resolvedText": "Confidential · Structural Engineering Team",
"inputDigest": "sha256:<digest>"
},
"digest": "sha256:<digest>"
}watermark.mode는 none 또는 required입니다. principal, Organization label이나 시각을 watermark에 넣을 경우 render 시점의 resolved text와 입력 digest를 snapshot에 먼저 고정합니다. renderer가 현재 clock, profile 또는 policy를 암묵적으로 다시 읽지 않습니다.
snapshot은 한 render의 byte 재현성을 고정하지만 이후 행동의 영구 allow를 저장하지 않습니다. policy, grant 또는 membership 변경은 관련 epoch/generation을 올리고 stale Preview, download, Print와 Publication allow를 다시 판정합니다. 기존 PDF artifact가 있다는 사실만으로 download 또는 external delivery를 허용하지 않습니다.
Report output authorization은 다음을 서로 다른 사용자 행동으로 판정합니다.
| 행동 | content capability | effective output policy |
|---|---|---|
| Report Preview | source Report의 artifact.read; 포함한 result/evidence에는 각각 read capability | pdfGenerationAllowed=true; durable output 없음 |
| Project PDF 생성·저장 | source Report의 artifact.read, artifact.export; 포함한 result/evidence read | pdfGenerationAllowed=true, projectPdfSaveAllowed=true |
| 선택 파일로 PDF export | source Report의 artifact.read, artifact.export; 포함한 result/evidence read | pdfGenerationAllowed=true, selectedFileExportAllowed=true |
| Project PDF 화면 읽기 | PDF artifact의 artifact.read | download와 분리 |
source Report의 artifact.read; 포함한 result/evidence에는 각각 read capability | printAllowed=true; 사용자 gesture 필수 | |
| Project PDF download | PDF artifact의 artifact.read, output.download | Organization/Workspace capability ceiling |
| ShareLink PDF download | linkSession AccessGrant의 artifact.read, output.download | sharedPdfDownloadMode=explicit_capability |
| Report Publication | publisher의 publication.create | pinned Publication policy; recipient PDF download에는 PublicationViewerSession의 explicit output.download |
Viewer나 Editor role만으로 PDF download, Print 또는 Publication을 허용하지 않습니다. output.download는 Project/ShareLink에서는 explicit AccessGrant, Publication에서는 explicit PublicationViewerSession capability를 요구하며 모두 current policy ceiling과 exact output target을 통과해야 합니다. 외부 ShareLink/Publication download는 sharedPdfDownloadMode=explicit_capability까지 요구하고 기본 off입니다. 이미 사용자가 내려받거나 인쇄한 외부 복사본은 이후 revoke로 회수할 수 없으므로 Plan에 이 비가역성을 표시합니다.
기본 policy는 external guest와 anonymous link를 deny합니다. enable은 impact Plan과 organization.policy.manage 승인을 요구합니다.
Policy precedence
한 요청은 다음 순서로 판정합니다.
- product hard safety deny와 suspended/closed Organization 상태 확인
- command kind에 맞는 Organization/Workspace/Project/Artifact/Link/PublicationViewer/LocalFile context와 session 확인
- Organization command이면 active OrganizationMembership만, Workspace command이면 active OrganizationMembership과 target WorkspaceMembership을 확인
- Project/Artifact command이면 두 membership과 resource custody를 확인; Link/PublicationViewer command이면 exact target·expiry·generation을 확인
- Organization capability 요청은 OrganizationMembership의 capability만, Workspace capability 요청은 WorkspaceMembership의 capability만 읽음
- Project/Artifact capability 요청은 active direct/inherited AccessGrant capability를 union
- exact ReviewRequestAssignment처럼 operation-scoped capability를 해당 revision에만 추가
- guest 또는 linkSession hard ceiling 적용
- OrganizationPolicy capability ceiling과 external/data/output policy 적용
- WorkspacePolicy ceiling 적용
- EntitlementLease/product capability와 quantitative limit 적용
- operation-specific revision, dependency, runtime, quota, secret와 resource safety constraint 적용
- 현재 authorization epoch/access generation을 확인하고 allow 또는 stable deny reason 반환
각 command는 capability namespace 하나만 요청할 수 있습니다. 위 OR는 한 command에서 organization/workspace/content 권한을 합친다는 뜻이 아니라 command kind에 맞는 branch 하나만 선택한다는 뜻입니다. OrganizationMembership capability로 Workspace 또는 content branch를 보충하거나, WorkspaceMembership capability로 Organization 또는 content branch를 보충하지 않습니다.
Project/Artifact capability와 product entitlement는 다른 namespace이므로 하나의 string set으로 교집합하지 않습니다. 예를 들어 artifact.read grant와 runtime.offline_run entitlement는 서로 대체하지 않으며, Run은 content authorization의 run.execute와 product entitlement/limit을 각각 통과해야 합니다.
Organization/Workspace administrative capability와 Project/Artifact content capability는 서로 union하지 않습니다. Organization owner에게 artifact.read를 암묵적으로 추가하거나 Project owner에게 organization.members.manage를 추가하지 않습니다.
authorizationEpoch과 revocation
Organization과 Workspace는 각 object의 authorizationEpoch을 소유합니다. Project와 Artifact는 다음 access state를 각각 하나씩 소유합니다.
{
"projectAccessState": {
"organizationRef": "<organization ref>",
"workspaceRef": "<workspace ref>",
"projectRef": "<project ref>",
"state": "active",
"generation": 9
},
"artifactAccessState": {
"projectRef": "<same project ref>",
"artifactRef": "<artifact ref>",
"state": "active",
"generation": 4
}
}Project/Artifact access state의 state는 active, archived, deleted closed enum이며 active ⇄ archived → deleted만 허용합니다. deleted는 terminal이고 같은 ref를 재사용하지 않습니다. archived resource는 read/recovery policy가 허용한 query 외의 write/Run/share mutation을 거부하고, deleted resource는 retention/legal-hold recovery 절차 외의 command를 거부합니다.
- Organization membership, Group, OrganizationPolicy, Organization state와 organization-wide security 변경은 Organization
authorizationEpoch을 증가시킵니다. - PrincipalSecurityState 변경은
principalSecurityEpoch을 증가시키고 그 principal의 모든 authenticated context를 재검증합니다. 이 epoch을 Organization epoch으로 대체하지 않습니다. - Workspace membership, WorkspacePolicy와 Workspace state 변경은 Workspace
authorizationEpoch을 증가시킵니다. - Project AccessGrant, Project stewardship와 Project lifecycle 변경은 exact ProjectAccessState
generation을 증가시킵니다. - Artifact-specific AccessGrant와 Artifact lifecycle 변경은 exact ArtifactAccessState
generation을 증가시킵니다. Project grant 상속도 판정하므로 Artifact command는 Project와 Artifact generation을 모두 pin하며, Project generation 변경을 모든 child row에 fan-out하지 않습니다. - ShareLink/linkSession은 자체
generation을 소유합니다. linkSession AccessGrant 변경은 해당 Project/Artifact access generation도 증가시킵니다. rotate/revoke/expiry는 link/session generation으로 즉시 무효화합니다. - sponsor membership revoke/expire는 Organization epoch을 증가시키고 해당 sponsor의 guest membership을 같은 transaction에서 revoke합니다. 그 guest의 하위 membership, group-derived/direct grant는 남아 있어도 사용할 수 없습니다.
- epoch과 generation은 감소하거나 재사용하지 않습니다. 변경 명령이 영향을 주는 모든 owner version을 같은 transaction에서 증가시킵니다. “필요하면 증가”하는 optional invalidation은 허용하지 않습니다.
- 모든 authenticated command는 principal security epoch을 먼저 pin합니다. 그 위에 Organization command는 Organization epoch, Workspace command는 Organization+Workspace epoch, Project command는 Organization+Workspace+Project generation, Artifact command는 여기에 Artifact generation을 추가로 pin합니다. Link command는 target tuple에 linkSession generation을 추가합니다.
- authorization decision cache, object/download ticket, Run/worker grant, SSE cursor, WebSocket, MCP session과 background task에는 command kind에 맞는 exact tuple을 pin합니다.
- pinned tuple이 현재 authority와 다르면 cached allow를 사용하지 않고 전체 판정을 다시 수행합니다.
- revoke/expiry는 cache purge 완료와 무관하게 요청 시점에 즉시 거부합니다.
- scheduler가 stored state를 아직 갱신하지 않았어도
now >= expiresAt이면 inactive입니다.
관리자 변경 Plan/Apply
허용 operation
관리 mutation은 다음 closed operation key를 사용합니다.
organization.membership.create
organization.membership.change
organization.membership.revoke
organization.member.offboard
organization.lifecycle.change
organization.policy.change
organization.security.change
organization.session.revoke
organization.sessions.revoke_all
organization.device.block
organization.device.allow
organization.ownership.transfer
workspace.create
workspace.membership.create
workspace.membership.change
workspace.membership.revoke
workspace.lifecycle.change
workspace.policy.change
workspace.ownership.transfer
project.lifecycle.change
artifact.lifecycle.change
group.create
group.members.change
group.revoke
access.grant.create
access.grant.change
access.grant.revoke
share_link.create
share_link.change
share_link.revoke
project.ownership.transfer
audit.export
legal_hold.create
legal_hold.releasegeneric acl.patch, role.patch, arbitrary JSON Patch와 client-side direct database update는 허용하지 않습니다.
모든 *.lifecycle.change의 desiredChange는 {toState, reasonCode} closed object입니다. toState는 owning lifecycle에서 허용된 다음 state만 사용할 수 있고 reasonCode는 배포가 관리하는 closed code입니다. Organization/Workspace의 closed 및 Project/Artifact의 deleted 전환 Plan은 active child/resource, 마지막 owner, pending Run, external delivery, retention/legal hold와 recovery/export 영향을 반드시 보여 주며 일반 resume command로 되돌릴 수 없습니다. Project archive/restore는 members.manage, Project delete는 project.delete, Artifact archive/restore는 artifact.write, Artifact delete는 project.delete를 요구합니다.
organization.membership.change는 role/capability 변경 또는 active ⇄ suspended 전환만 사용합니다. terminal 제거는 organization.membership.revoke, 소유권·guest sponsor·Workspace/Project 책임·seat·session·예약 작업을 함께 정리해야 하는 사람 제거는 organization.member.offboard를 사용합니다. offboard를 여러 unrelated revoke command로 나눠 orphan owner나 살아 있는 session을 만들지 않습니다.
organization.session.revoke, organization.sessions.revoke_all, organization.device.block|allow는 위 Organization-scoped control만 변경합니다. 다른 Organization 또는 전역 account session을 target으로 resolve하면 거부합니다.
Plan request와 result
{
"operation": "access.grant.change",
"actorContext": "<verified active context>",
"target": "<semantic target resolved by server>",
"expected": {
"kind": "artifact",
"organizationGeneration": 12,
"organizationPolicyRevision": 7,
"organizationAuthorizationEpoch": 31,
"workspaceGeneration": 9,
"workspacePolicyRevision": 4,
"workspaceAuthorizationEpoch": 18,
"projectAccessGeneration": 9,
"artifactAccessGeneration": 4,
"targetVersions": [
{
"kind": "access_grant",
"targetRef": "<opaque grant ref resolved by server>",
"generation": 1
}
]
},
"desiredChange": "<operation-specific closed object>",
"idempotencyKey": "<client generated opaque key>"
}expected는 다음 closed scope variant 중 하나입니다.
organization = { kind, organizationGeneration, organizationPolicyRevision, organizationAuthorizationEpoch, targetVersions[] }
workspace = { organization fields, workspaceGeneration, workspacePolicyRevision, workspaceAuthorizationEpoch, targetVersions[] }
project = { workspace fields, projectAccessGeneration, targetVersions[] }
artifact = { project fields, artifactAccessGeneration, targetVersions[] }targetVersions[]는 targetRef 기준 정렬·중복 없는 array이며 existing target을 바꾸거나 revoke할 때 영향받는 모든 row의 {kind, targetRef, generation}을 담습니다. kind는 organization_membership, workspace_membership, policy, group, group_membership, access_grant, share_link, organization_session_control, organization_device_admission, legal_hold closed enum입니다. create operation에는 아직 target이 없으므로 빈 array를 사용합니다. targetRef는 server가 semantic target을 resolve한 결과이며 AI/MCP가 임의로 조립하지 않습니다. ShareLink change/revoke는 kind=share_link을 pin하고 commit 시 derived linkSession을 무효화합니다. 적용되지 않는 scope field, null, wildcard와 “latest”를 concurrency 우회값으로 사용하지 않습니다.
Plan result는 다음을 모두 포함합니다.
- normalized operation과 exact target
- before/after capability diff
- affected principal/group/guest/link session 수
- invalidated session, ticket, Run과 background access summary
- offboarding일 때 Organization/Workspace/Project ownership, sponsored guest, group, review, 예약 작업, seat와 보존 영향
- dependency, download/export와 external sharing impact
- last-owner/stewardship validation
- policy/entitlement constraint와 denied field
- expected generation/authorization tuple
planDigest,expiresAt와 stable warnings
Plan은 state를 변경하지 않습니다. 권한이 없는 resource 이름과 존재를 warning으로 누출하지 않습니다.
Apply
Apply는 planDigest, 동일한 normalized change, expected generation tuple, idempotency key와 명시적 사용자 확인을 요구합니다.
- Plan이 만료됐거나 generation/epoch이 달라졌으면 전체 Apply를 거부합니다.
- mutation, generation compare-and-swap, audit append와 outbox append는 한 authoritative transaction에서 모두 commit하거나 모두 rollback합니다. audit/outbox 내부 append 순서는 observable contract가 아닙니다.
- 일부 recipient만 변경하고 나머지를 성공처럼 표시하지 않습니다.
- timeout으로 결과가 불명확하면 성공을 추정하지 않고 operation status를 조회합니다.
- 같은 idempotency key와 digest 재전송은 같은 committed result를 반환합니다.
- AI/MCP도 같은 Plan/Apply와 사용자 승인을 사용하며 internal ref, raw token과 capability array를 임의로 조립하지 않습니다.
마지막 owner 보호
stewardship은 권한 role의 두 번째 원본이 아니라 “마지막 책임자를 제거하지 않기 위한 governance marker”입니다.
organization_ownerstewardship은 active humanmember의 OrganizationMembership에만 허용되며grantedCapabilities가 exact Organizationownerpreset의 모든 capability를 포함해야 합니다.workspace_ownerstewardship은 active human principal의 WorkspaceMembership에만 허용되며grantedCapabilities가 exact Workspaceownerpreset의 모든 capability를 포함해야 합니다.project_ownerstewardship은 active human principal의 direct Project AccessGrant에만 허용되며grantedCapabilities가 exact Projectownerpreset의 모든 capability를 포함해야 합니다.- active Organization, Workspace와 Project에는 위 조건을 만족하는 owner가 각각 최소 하나 있어야 합니다. marker만 있고 관리·이전 capability가 없는 powerless owner는 schema validation에서 거부합니다.
- effective owner governance floor는 Organization에
organization.members.manage,organization.policy.manage,organization.ownership.transfer, Workspace에workspace.members.manage,workspace.policy.manage,workspace.ownership.transfer, Project에members.manage,ownership.transfer입니다. - Organization/Workspace policy Plan은 변경 후 capability ceiling을 적용한 effective owner를 다시 계산합니다. 위 floor가 하나라도 제거되거나 scope별 effective owner 수가 0이 되면 policy Apply를
PX_LAST_OWNER_REQUIRED로 거부합니다. stored marker나 preset만으로 통과시키지 않습니다. - Group, guest, service, device와 linkSession은 마지막 owner가 될 수 없습니다.
- 마지막 owner를 revoke, expire, demote 또는 제거하는 단일 command는 거부합니다.
- ownership transfer는 새 owner 생성/승격, capability와 policy 검증, 기존 owner 변경을 한 Plan과 transaction에서 수행합니다.
- destination owner가 이미 active인지, suspended/revoked 상태인지와 affected dependency/share를 Plan에 표시합니다.
- break-glass recovery는 일반 role command가 아니라 별도 service-admin 절차이며 사유, 승인자와 전체 audit를 요구합니다. service-admin이 content를 자동 열람하지 않습니다.
Audit 계약
AuditRecord
Security/access audit는 append-only이며 business state replay나 notification authority가 아닙니다.
{
"auditRef": "<opaque audit ref>",
"organizationRef": "<organization ref>",
"workspaceRef": "<optional workspace ref>",
"occurredAt": "2026-07-21T00:00:00Z",
"actorPrincipalRef": "<opaque principal ref>",
"actorKind": "human",
"action": "access.grant.change",
"target": {
"kind": "artifact",
"targetRef": "<opaque target ref>"
},
"authorization": {
"decision": "allowed",
"capability": "members.manage",
"organizationPolicyRevision": 7,
"workspacePolicyRevision": 4,
"organizationAuthorizationEpoch": 31,
"workspaceAuthorizationEpoch": 18
},
"beforeDigest": "sha256:<digest>",
"afterDigest": "sha256:<digest>",
"planDigest": "sha256:<digest>",
"correlationRef": "<opaque correlation ref>",
"outcome": "committed",
"retentionClass": "governance",
"retentionPolicyRef": "<versioned retention policy ref>",
"retentionPolicyRevision": 6,
"retainUntil": "2033-07-21T00:00:00Z",
"legalHoldRefs": []
}Closed values:
actorKind: Principal의 closedhuman,service,device와 같은PrincipalKindvocabularyauthorization.decision:allowed또는deniedoutcome:committed,rejected,indeterminate,reconciledretentionClass:security_access,governance,operational
Organization-scope event는 workspaceRef, workspacePolicyRevision과 workspaceAuthorizationEpoch을 생략합니다. Workspace/Project/Artifact event에서는 세 field가 필수입니다. unqualified policyRevision을 AuditRecord authorization에 사용하지 않습니다.
AuditRecord input은 free-form string을 받지 않습니다. 허용되는 scalar는 typed_ref, public_digest_ref, utc_timestamp, unsigned_revision, closed_enum뿐이고 각 field는 해당 typed constructor를 통해서만 만들어집니다. document/content/credential/path/stack trace를 문자열 denylist나 heuristic으로 분류하지 않습니다. 그러한 자유 텍스트를 받는 constructor 자체가 audit record에 없습니다.
각 audit producer는 record를 내보내기 전에 {producerRef, emittedMembers} manifest를 한 번 선언합니다. emittedMembers는 non-empty sorted unique 목록이고 아홉 required member를 모두 포함합니다. 같은 manifest 재선언은 기존 manifest를 반환하고 다른 목록 재선언은 거부합니다. manifest가 선언한 optional member가 row에 없으면 no_value, manifest가 선언하지 않은 member가 없으면 producer_does_not_emit이므로 consumer는 빈 값을 추측하지 않습니다.
legalHoldRefs는 sorted unique 0..N LegalHoldRef 목록입니다. 빈 목록은 hold가 없다는 뜻이고, 두 hold를 delimiter string 하나에 합치거나 마지막 hold로 덮어쓰지 않습니다.
필수 audit action:
- ExternalIdentityBinding과 PrincipalSecurityState의 security-sensitive change
- Organization/Workspace membership create/change/revoke
- member suspend/reactivate/offboard와 Organization-scoped session/device revoke
- guest accept/expire/revoke와 Group membership change
- custody, policy, ownership와 stewardship change
- AccessGrant create/change/revoke와 ShareLink/linkSession rotate/revoke
- important authorization denial
- artifact export/source download
- Run/worker grant mint와 security-sensitive cancel
- Publication, Embed와 external effect
- audit query escalation, audit export와 legal hold change
- break-glass operation
모든 성공 read를 무차별 기록하지 않습니다. audit record에는 다음을 넣지 않습니다.
- document body, table, Flow input/result payload와 prompt
- raw email, invitation/share/link/password/token과 bearer credential
- filesystem path, object provider key, stack trace와 secret
- before/after full object body
actor/target profile label이 필요하면 현재 권한으로 별도 profile projection에서 resolve합니다. audit 자체에는 opaque ref와 digest를 보존합니다.
Audit 조회와 내보내기
audit.query는organization.audit.read를 요구하고 active Organization context 안에서만 동작합니다.- query filter는 time range, action, outcome, workspace, target kind와 safe principal ref로 제한합니다.
- Organization audit capability는 Project content access를 부여하지 않습니다. event metadata만 반환합니다.
audit.export는organization.audit.export와 Plan/Apply를 요구합니다.- export Plan은 범위, record 수/크기, redaction profile, retention, destination과 영향받는 legal hold를 보여 줍니다.
- export 결과는 immutable object와 expiring scoped download ticket으로 전달하며 raw storage path를 노출하지 않습니다.
- audit export의 생성·다운로드·expiry도 audit에 남깁니다.
- caller가 볼 수 없는 다른 Organization, secret과 content는 export에 포함하지 않습니다.
Retention과 legal hold
RetentionPolicy는 각 retentionClass에 양의 보존 기간과 purge rule을 매핑하는 versioned Organization policy입니다.
- AuditRecord append 시
retentionClass,retentionPolicyRef,retentionPolicyRevision과 계산된retainUntil을 고정합니다. - policy 변경은 기존 record 보존을 자동 단축하지 않습니다. 단축은 별도 impact Plan과 법적·관리 승인 후에만 적용합니다.
- retention expiry는 즉시 삭제 성공을 뜻하지 않습니다. fenced durable purge task와 deletion receipt가 authority입니다.
- purge receipt는 별도 append-only object이며 exact member는
receiptRef,organizationRef,targets,retentionPolicyRef,retentionPolicyRevision,actorPrincipalRef,purgedAt,idempotencyKey입니다. 각 target은auditRef,recordDigest이고 non-empty sorted unique set입니다. 같은 idempotency key의 동일 재시도는 기존 receipt를 반환하고 다른 body 재사용은 거부합니다. - active legal hold가 있는 record는
retainUntil이 지나도 삭제하지 않습니다. - legal hold는 별도 저장된
LegalHoldScope에 붙습니다. scope exact member는scopeRef,organizationRef, optionalworkspaceRef,selector, optionaloccurredAtFrom, optionaloccurredAtThrough입니다. selector kind는all_records,audit_record,record_kind이고 단일 record 또는 record kind hold는 scope의 특수 경우입니다. - legal hold object exact member는
legalHoldRef,scopeRef,reasonRef,caseRef,state,createdBy,createdAt, optionalreleasedBy, optionalreleasedAt입니다. state는active또는released이고 delete로 history를 지우지 않습니다. organization.legal_hold.manage없이 hold를 생성·해제할 수 없습니다.PX_LEGAL_HOLD_CONFLICT는 존재하지 않는 hold release와 이미 purge된 record에 붙는 scope의 hold attach 두 사건만 뜻합니다. active hold 때문에 purge가 record를 skip하는 것은 hold-wins 정상 결과이고 conflict가 아닙니다.- account/profile erasure는 raw profile을 제거하거나 pseudonymize할 수 있지만 audit의 actor ref, action, digest와 hold evidence를 조작하지 않습니다.
- security audit, user-facing Activity, application log와 provider control-plane log를 같은 stream이나 retention으로 합치지 않습니다.
Local 동작
Local 제품도 같은 principal, personal Organization/Workspace, membership, capability와 AccessGrant 의미를 사용합니다.
사용자가 OS 파일 선택기, 파일 연결의 double-click 또는 명시적 Save As로 연 unmanaged portable file은 관리 Project가 아닙니다. 이 경우 Project AccessGrant를 위조하지 않고 다음 별도 authority context를 사용합니다.
LocalFileCommandContext = {
kind: "local_file",
fileAuthorityRef,
authoritySource: "os_open_grant" | "os_save_grant",
fileFingerprint,
mode: "edit" | "recovery_read_export",
generation,
optional principalRef,
optional authenticationSessionRef
}fileAuthorityRef는 OS가 앱에 부여한 exact file handle/bookmark를 감싼 account/device-local ref이며 portable file, URL, log와 MCP target에 저장하지 않습니다. raw path는 authorization identity가 아닙니다.
editmode는 signed-in principal과 유효한 product entitlement를 요구하며 exact file의 open/read/write/Save As만 허용합니다.logout, entitlement expiry 또는 managed membership revoke 뒤에는 사용자가 소유한 external file에 한해
recovery_read_exportmode를 허용할 수 있습니다. 이 mode는 read, backup과 Save As만 허용하고 Run, dependency resolve, MCP/AI write, cloud sync, Share, Publication과 managed Recent mutation을 금지합니다.unmanaged file을 Project로 import하거나 Cloud에 copy할 때 destination Organization/Workspace의 normal Plan/Apply, new identity와 owner AccessGrant를 만듭니다. LocalFileCommandContext를 Cloud authorization으로 전달하지 않습니다.
managed Project command에는 LocalFileCommandContext를 사용할 수 없고, unmanaged file command에는 Organization/Workspace/AccessGrant snapshot을 가짜로 만들지 않습니다.
first productive use 전에 account principal과 signed entitlement를 확인합니다.
Personal Organization/Workspace bootstrap은 local authority의 transaction과 audit를 거칩니다.
local managed Project도 direct project-owner stewardship grant를 만듭니다.
외부 core
.pxflow·.pxproject또는 설치한 Component의 artifact 파일을 열었다는 이유로 membership/grant를 파일 안에 쓰거나 관리 Project로 자동 import하지 않습니다.portable package에는 Organization/Workspace ref, membership, AccessGrant, policy, audit, account와 token을 포함하지 않습니다.
Local→Cloud는 destination Organization/Workspace에서 새 Project/Artifact identity, owner grant와 lineage를 만드는 명시적 copy/import입니다.
cached entitlement가 유효해도 다른 account의 local managed workspace, Recent와 policy snapshot을 읽지 않습니다.
account switch는 unsaved work를 확인하고 account-scoped session, policy/access cache와 active context를 닫은 뒤 새 principal로 다시 엽니다.
offline local managed Project command는 유효한 EntitlementLease와 scope·epoch·expiry가 있는 local membership/grant snapshot을 모두 확인합니다. unmanaged file은 위 LocalFileCommandContext 규칙을 사용합니다.
online principal security 또는 Organization membership이 suspend/revoke된 뒤 reconnect하면 stale local allow를 server에 upload하지 않습니다. 사용 가능한 local recovery copy와 access 변경을 설명합니다.
license 만료·logout·principal security suspension·membership suspend/revoke는 사용자가 소유한 외부 파일을 삭제하지 않습니다. 허용된 read/export/backup/recovery 경로를 유지합니다.
Stable diagnostic
| code | 의미 | 사용자 조치 |
|---|---|---|
PX_ORGANIZATION_CONTEXT_REQUIRED | active Organization이 없음 | 접근 가능한 Organization 선택 |
PX_ORGANIZATION_SCOPE_MISMATCH | target이 active Organization과 다름 | 올바른 Organization/Workspace에서 다시 열기 |
PX_PRINCIPAL_SECURITY_SUSPENDED | 전역 account security state가 productive command를 차단 | 계정 또는 identity provider 관리자에게 확인 |
PX_ORGANIZATION_SUSPENDED | Organization이 suspended | 조직 관리자에게 상태 확인 |
PX_ORGANIZATION_CLOSED | Organization이 terminal closed | 허용된 recovery/export 절차 확인 |
PX_ORGANIZATION_MEMBERSHIP_REQUIRED | active OrganizationMembership이 없음 | 조직 관리자에게 참여 요청 |
PX_ORGANIZATION_MEMBERSHIP_SUSPENDED | 이 Organization membership이 일시 정지됨 | 조직 관리자에게 재개 요청 |
PX_ORGANIZATION_MEMBERSHIP_INACTIVE | OrganizationMembership이 revoked/expired | 상태 확인 또는 다른 Organization 선택 |
PX_WORKSPACE_CONTEXT_REQUIRED | Workspace command에 target Workspace가 없음 | Workspace 선택 |
PX_WORKSPACE_SUSPENDED | Workspace가 suspended | Workspace 관리자에게 상태 확인 |
PX_WORKSPACE_CLOSED | Workspace가 terminal closed | 허용된 recovery/export 절차 확인 |
PX_WORKSPACE_MEMBERSHIP_REQUIRED | target Workspace membership이 없음 | Workspace 참여 요청 |
PX_WORKSPACE_MEMBERSHIP_SUSPENDED | WorkspaceMembership이 일시 정지됨 | Workspace 관리자에게 재개 요청 |
PX_WORKSPACE_MEMBERSHIP_INACTIVE | WorkspaceMembership이 revoked/expired | Workspace 관리자에게 상태 확인 |
PX_PROJECT_ARCHIVED | Project가 archived이며 productive command가 차단됨 | restore 권한 요청 또는 read/recovery 사용 |
PX_PROJECT_DELETED | Project가 terminal deleted | retention/legal-hold recovery 절차 확인 |
PX_ARTIFACT_ARCHIVED | Artifact가 archived이며 write/Run/share가 차단됨 | restore 권한 요청 또는 read/recovery 사용 |
PX_ARTIFACT_DELETED | Artifact가 terminal deleted | retention/legal-hold recovery 절차 확인 |
PX_ACCESS_GRANT_REQUIRED | target resource capability grant가 없음 | Project owner에게 필요한 접근 요청 |
PX_CAPABILITY_DENIED | requested capability가 grant에 없음 | 필요한 role/capability 요청 |
PX_POLICY_DENIED | Organization/Workspace policy ceiling이 차단 | 정책 관리자에게 문의 |
PX_ENTITLEMENT_CAPABILITY_REQUIRED | 필요한 product capability가 없음 | 이용권 또는 기능 access 확인 |
PX_ENTITLEMENT_SEAT_REQUIRED | Organization seat가 배정되지 않음 | 관리자에게 seat 요청 |
PX_ENTITLEMENT_LIMIT_EXCEEDED | 정량 limit 또는 usage reservation이 부족 | 실행 수·저장량·사용량 확인 |
PX_ENTITLEMENT_RENEWAL_REQUIRED | lease가 만료됐거나 갱신이 필요함 | 로그인·offline activation 갱신 |
PX_AUTHORIZATION_STALE | epoch/generation이 변경됨 | 상태를 새로 읽고 작업 재시도 |
PX_ORGANIZATION_SESSION_REVOKED | 이 Organization의 session control이 revoke됨 | 다시 인증하거나 관리자에게 확인 |
PX_ORGANIZATION_DEVICE_BLOCKED | 이 Organization에서 현재 device 사용이 차단됨 | 허용된 기기 사용 또는 Security Admin 문의 |
PX_GUEST_EXPIRED | guest membership이 만료됨 | sponsor에게 재초대 요청 |
PX_GROUP_MEMBERSHIP_INVALID | group member scope/state가 유효하지 않음 | Group membership 수정 |
PX_LINK_SESSION_EXPIRED | link session 만료·회전·철회 | 새 공유 링크 요청 |
PX_LINK_TARGET_MISMATCH | linkSession target과 요청 resource가 다름 | 공유된 target에서 다시 열기 |
PX_PUBLICATION_SESSION_EXPIRED | Publication viewer session이 만료·중단·철회됨 | 현재 Publication에서 다시 열기 |
PX_PUBLICATION_TARGET_MISMATCH | Publication Version/PDF rendition target이 session과 다름 | 게시된 exact output에서 다시 열기 |
PX_REVIEW_ASSIGNMENT_REQUIRED | exact revision의 active ReviewRequestAssignment가 없음 | 검토 요청 담당자·상태 확인 |
PX_LOCAL_FILE_AUTHORITY_REQUIRED | exact external file의 OS authority가 없음 | 파일 선택기 또는 파일 연결로 다시 열기 |
PX_ROLE_INVALID | scope에 없는 role 또는 capability 조합 | 허용된 role/capability 선택 |
PX_ROLE_DELEGATION_DENIED | actor가 지정할 수 없는 조직 역할 또는 capability 상승 | Owner에게 역할 변경 요청 |
PX_LAST_OWNER_REQUIRED | 마지막 owner 제거 시도 | 새 owner를 같은 transfer Plan에 지정 |
PX_ADMIN_PLAN_STALE | Plan 이후 state/generation 변경 | 변경 영향을 다시 Plan |
PX_ADMIN_PLAN_EXPIRED | Plan 승인 시간이 만료됨 | 새 Plan 생성 |
PX_AUDIT_PERMISSION_DENIED | audit read/export capability 없음 | Security admin/Owner에게 요청 |
PX_LEGAL_HOLD_CONFLICT | 존재하지 않는 hold release 또는 이미 purge된 record에 hold attach | hold ref와 purge receipt 확인 |
Diagnostic detail에는 권한 없는 Organization/Workspace/Project 이름, 다른 principal의 email, raw internal mapping, token, path와 policy secret을 넣지 않습니다. HTTP status, CLI exit와 MCP error가 같은 code와 safe action을 사용합니다.
Conformance gate
access-policy.v1.cases.json fixture의 expectedReason은 causal test가 어떤 입력을 바꿀지 설명하는 non-normative prose이며 stable diagnostic, enum 또는 wire contract가 아닙니다.
제품은 다음 적합성 시나리오를 모두 충족해야 합니다.
- 한 principal이 두 Organization에 참여해도 active context 밖의 Project, search, thumbnail, cache, Result와 object가 0건 노출됩니다.
- 한 human principal에 여러 검증된 ExternalIdentityBinding을 연결해도 같은 principalRef를 유지하고 email/domain 일치만으로 서로 다른 principal을 병합하지 않습니다.
- principal security suspension이 모든 authenticated productive context를 차단하되 한 Organization의 membership suspension은 다른 Organization 접근을 바꾸지 않습니다.
- Organization owner/admin/security admin이 별도 WorkspaceMembership과 AccessGrant 없이 Project content를 읽지 못합니다.
- Workspace owner/admin/member가 AccessGrant 없이 Project 목록·content·Result를 읽지 못합니다.
- Personal Organization/Workspace bootstrap이 실제 membership, owner stewardship, management source, epoch와 audit를 한 transaction으로 만듭니다.
- project role preset이 명시한 exact capability set으로 확장되고 preset label이 두 번째 authority가 되지 않습니다.
Reviewerproject role과 unscoped review capability가 schema에서 거부되고 exact ReviewRequest assignee만 pinned revision에 결정할 수 있습니다.- EntitlementLease에 범위 없는
roles[], Project/Workspace role, grant와 artifact identity가 있으면 검증을 거부합니다. - Organization membership revoke가 하위 WorkspaceMembership, principal/group grant, HTTP/WebSocket/MCP/object/Run ticket을 즉시 차단합니다.
- Group member change가 cached group-derived access를 epoch mismatch로 무효화합니다.
- guest expiry/sponsor revoke 뒤 direct/group grant가 남아 있어도 access가 거부됩니다.
- anonymous linkSession이 write/comment/review/run/export/source/share capability를 얻지 못합니다.
- OrganizationPolicy보다 넓거나 긴 WorkspacePolicy, guest와 link 설정은 저장 전에 거부됩니다.
- direct/inherited AccessGrant는 union되고 policy/entitlement/operation ceiling과 expiry가 항상 적용됩니다.
- Plan과 Apply 사이 generation/epoch이 바뀌면 부분 변경 없이
PX_ADMIN_PLAN_STALE로 거부됩니다. - 마지막 Organization/Workspace/Project owner 제거는 거부되고 atomic transfer만 성공합니다.
- membership/grant/policy mutation과 audit/outbox가 같은 transaction에서 commit되거나 모두 rollback됩니다.
- Audit query/export가 content, raw email/token/path를 포함하지 않고 다른 Organization record를 반환하지 않습니다.
- retention expiry가 active legal hold를 우회하지 않고 purge receipt 전에는 삭제 완료로 표시되지 않습니다.
- portable core
.pxflow·.pxproject와 설치한 Component의 artifact에 Organization/Workspace ref, membership, role, grant, policy, entitlement, audit와 raw token이 0건입니다. - Local account switch와 offline/reconnect에서 다른 account metadata 및 stale revoked access가 교차하지 않습니다.
- desktop-local, server-local과 cloud-hosted adapter가 같은 allow/deny, epoch invalidation과 stable diagnostic fixture를 통과합니다.
- Organization 관리/audit command는 Workspace 선택 없이 exact OrganizationMembership capability로 동작하고, Organization role은 content branch에 합쳐지지 않습니다.
auditorpreset은 policy와 audit metadata만 읽고 별도 WorkspaceMembership/AccessGrant 없이 Project 이름·content·Result를 0건 반환합니다.- owner stewardship이 owner preset의 모든 필수 capability 없이 저장되지 않고, powerless marker가 마지막 owner invariant를 만족시키지 못합니다.
- Project grant 변경은 Project access generation, Artifact-specific grant 변경은 Artifact access generation을 올리며 Artifact command가 두 generation 중 하나라도 stale이면 거부됩니다.
- Plan이 command scope의 Organization/Workspace epoch와 Project/Artifact/link generation을 모두 pin하고 하나라도 바뀌면 Apply가 전체 거부됩니다.
- Viewer/Editor role만으로
output.download, Print 또는 Publication이 허용되지 않고 explicit scoped authority와 current policy가 요구되며 external delivery는 pinned output policy도 검증합니다. - output policy snapshot의 classification, resolved watermark와 digest가 고정되며 renderer가 현재 policy/profile/clock을 암묵적으로 읽지 않습니다.
- Organization/Workspace lifecycle Plan, sponsor revoke cascade와 inactive-state diagnostic이 모든 adapter에서 같은 결과를 냅니다.
- unmanaged external file은 LocalFileCommandContext로만 열리고 managed/Cloud access로 승격되지 않으며 recovery mode에서 Run·AI write·Share가 거부됩니다.
- Project/Artifact archive·restore·delete가 closed transition과 capability를 따르고 해당 access generation 및 stable diagnostic을 갱신합니다.
- public/unlisted Publication PDF는 exact Publication Version/rendition session에 explicit
output.download가 있을 때만 내려받고 source Project를 탐색하지 못합니다. - Admin이 delegation matrix 밖의 Owner·Security Admin·Billing Admin·Auditor를 지정하거나 client capability array로 권한을 상승시키지 못합니다.
- membership suspend/reactivate가 같은 Organization의 epoch과 session을 재검증하고 offboarding은 owner·sponsor·seat·예약 작업을 정리하지 않으면 전체 거부됩니다.
- Organization session revoke와 device block이 target Organization에서만 적용되고 다른 Organization 또는 전역 account session을 변경하지 않습니다.
- policy 변경은 기존 revision body를 수정하지 않고 새 immutable revision과 active pointer를 만들며 pointer·generation·authorization epoch·audit·outbox가 한 transaction에서 함께 바뀝니다.
Repository boundary conformance inventory
V1 inventory는 기존 열네 pure core fixture ID와 absent-named-grant repository read 하나로 닫힙니다. 열네 ID는 browser-transport-application-query, desktop-transport-application-command, filesystem, database, object-store, queue, cloud-sdk, DOM/component/editor-state 거부 일곱 건, repository와 infrastructure responsibility separation 두 건, desktop/server/AWS composition selection 세 건입니다. Repository read ID는 absent-named-grant-repository-read입니다. 기존 caller test name은 evidence label로 유지하며 rename하지 않습니다.
금지되는 구현
- Organization과 tenant를 별도 product identity로 두거나 N:M mapping
- human principal과 같은 사람을 가리키는 별도 account identity를 만들거나 email/domain 일치만으로 principal 자동 병합
- email, domain, workspace path, provider account와 URL slug를 Organization identity로 사용
- global current Organization을 process singleton으로 두고 command scope 생략
- Organization/Workspace admin에게 content read를 암묵 부여
- Organization Admin이 사용자의 전역 비밀번호·MFA·principal security state 또는 다른 Organization session을 변경
- invitation, Organization membership, guest expiry, seat와 account security를 하나의 모호한 user status로 저장
- Organization membership suspension을 전역 account suspension으로 처리하거나 다른 Organization에 전파
Reviewer를 Project role로 저장review.decide를 AccessGrant 또는 role preset에 저장- EntitlementLease의 범위 없는
roles[]로 Project permission 복원 - role label과 capability array를 두 개의 authorization authority로 사용
owner_email,allowedUsers[], ACL blob,shared/publicboolean과 creator를 AccessGrant로 사용- guest를 email-only pseudo principal로 만들거나 expiry/sponsor 없이 유지
- anonymous link를 Commenter/Editor/Admin/Owner로 승격
- nested Group과 cross-Organization Group member
- revoke를 cache purge 또는 scheduler 완료 전까지 지연
- UI에서 버튼을 숨기는 것만으로 permission enforcement
- generic patch, optimistic success와 사용자 승인 없는 ownership/policy/share 변경
- 마지막 owner 제거 뒤 service-admin을 자동 owner로 지정
- owner capability 없이 stewardship marker만 저장
- Viewer/Editor role만으로 PDF download, Print 또는 Publication 허용
- output policy snapshot 없이 현재 policy/profile/clock을 render 중 다시 읽기
- audit에 document body, raw token, email, path, before/after object 전체 저장
- Activity projection, search index, cache와 provider log를 audit authority로 사용
- portable artifact에 Organization, membership, grant, entitlement와 audit 저장