본문으로 건너뛰기
참조

Resource 계약

App이 소유하는 Resource의 생성기 시그니처와 Resource Module 계약, 생성 결과, 검증 오류를 정확히 다룹니다.

Resource 계약

Dashboard composition field

publicForBuilder는 Resource를 catalog 검토 대상으로 만들 뿐 모든 descriptor field를 공개하거나 row를 인가하지 않습니다. 기존 read path가 노출해도 되는 직접 field에만 composition.selectable, groupable, aggregate operation, time bucket을 선언하세요. custom scope, redaction, projection이 필요하면 CompositionQueryContribution을 구현합니다. Provider는 복원된 actor와 정확한 조직 target으로 AuthorizedCompositionQuery를 반환하고 Core는 그 alias만 사용합니다. Resource read permission과 predicate를 재사용하며 Dashboard permission이나 client SQL을 추가하지 마세요.

생성 작업 순서는 Resource 만들기를 따르세요. 이 문서는 명령 옵션과 생성된 module의 식별자·모델·권한·내비게이션·조회 필드 계약을 설명합니다. Module이 발견되는 것만으로 실행 권한이 증명되지는 않으므로 아래 권한 불변 조건을 유지하세요.

통화 금액 필드의 composition metadata 또는 provider fields에서 aggregations, unit과 함께 required_dimensions: ['currency']를 선언하세요. 같은 source의 해당 필드는 groupable이어야 합니다. Core는 그 필드를 버킷 없는 dimension으로 선택했거나 전체 조회 또는 measure의 AND 조건에서 단일 스칼라 eq로 제한한 경우에만 값 집계를 허용합니다. OR/NOT/IN은 이 조건을 충족하지 않습니다. 계산식과 비교 조회에도 동일하게 적용되며 count와 distinct-count는 제외됩니다. 통화 환산 기능은 아닙니다.

즐겨찾기 대상 메타데이터

Core는 standard Resource에 catalog model, 선언된 .read permission, standard stored-owner authorization contract, Shell Resource의 show shape, 선언된 destination이 모두 있을 때만 favorite_target metadata를 낼 수 있습니다. 이 metadata는 서버가 검증한 record identity이며 route나 presentation snapshot이 아닙니다. FavoriteButton과 함께 사용하고 list URL이나 model class에서 다시 만들지 마세요.

App은 favorite identity로 state, title, URL을 저장하거나 보내지 않습니다. Core는 즐겨찾기를 읽거나 바꿀 때마다 현재 actor 기준으로 identity를 다시 해석합니다. 이 해석 뒤 owner summary는 현재 인가된 title과 route를 presentation으로 제공할 수 있습니다. standard App Resource도 Core Resource와 같은 exact-record authority path를 사용합니다. custom App Resource는 현재 display와 canonical reentry route를 제공하는 기존 authorization-safe ResourceSummaryContribution을 사용할 수 있습니다. 예를 들어 People Worker summary는 page organization 선택이 없는 즐겨찾기에서 Worker visibility scope와 record policy로 대상을 해석하고, 명시적인 organization 선택에서는 더 좁은 relationship rule을 그대로 적용합니다.

ResourceRef의 canonical identity metadata는 Core가 candidate를 분류할 App과 fully-qualified resource key를 나타냅니다. record를 인가하거나 favorite를 만들지는 않습니다. 알 수 없는 key, 지원하지 않는 discriminator, 없는 route, 현재 authorization 검사에 실패한 record는 null로 해석됩니다. 열리게 하려고 read favorite를 edit route로 연결하지 마세요.

이 capability를 제공할 수 있는 목록 응답은 meta.list_schema.resource_key와 favorites.only filter를 노출할 수 있습니다. 이 key는 FavoriteTarget과 같은 canonical record key이며 host는 mutation 뒤 활성 즐겨찾기만 보기 목록을 새로 고칠 때 사용합니다. App은 목록 schema를 그대로 전달하고 route에서 cache key를 만들지 않습니다. 공개되는 filter option은 일반 목록 option인 { value: "1", label: "favorites.only" }이며, favorite-only contribution, endpoint, 병렬 pagination 계약을 추가하지 마세요.

primary cell이 관련 Resource로 연결되지만 행의 owner를 나타내면 owner identity를 favoriteCandidate로 전달하세요. host는 status endpoint에서 candidate를 먼저 인가한 뒤에만 button을 표시합니다. star가 생기면 안 되는 관련 record cell에는 favoriteable={false}를 설정하세요. 응답에 인가된 favorite_target metadata가 이미 있을 때만 favoriteTarget을 사용하며, App이 target을 인가하는 근거가 되지는 않습니다.

시그니처

App Resource를 만들어내는 생성기입니다.

nexia-apps:make-package-resource
    {package}                        기존 패키지 app key 또는 표시 이름 (예: quality-inspection)
    {name}                           PascalCase Resource 이름 (예: WorkOrder)
    --icon=box                       App 안 Resource 내비게이션 항목의 Shell 아이콘 이름
    --sort=                          App 안 내비게이션 정렬 순서, 기본값은 다음 자리
    --navigation-group=operations    insights | management | operations | master-data | settings
    --navigation-subgroup=           선택적 App 소유 하위 그룹 id
    --record-owner=legal_entity      정본 레코드 소유자 (tenant | legal_entity)
    --label-ko=                      작성한 한국어 단수 라벨
    --label-ko-plural=               작성한 한국어 복수·컬렉션 라벨
    --label-zh=                      작성한 중국어 단수 라벨
    --label-zh-plural=               작성한 중국어 복수·컬렉션 라벨
    --without-navigation             Route와 Catalog는 있고 App Menu 항목은 없음
    --dry-run                        쓰지 않고 파일과 패키지 내 편집을 보고
    --force                          생성된 Resource 파일을 제자리에서 다시 씀

생성되는 Resource Module은 AbstractResourceModule을 상속하고 같은 Resource의 Catalog facet을 구현합니다. public descriptor는 단일 AppDescriptorContribution set으로 공개합니다.

코드 예시
PHP
use Nexia\AppRuntime\Contracts\ShellResourceContribution;
use Nexia\Contribution\AbstractResourceModule;              // + appDescriptors() set
use Nexia\Contribution\Contracts\ResourceAuthorizationContribution;   // + resourceAuthorization()
use Nexia\Contribution\Contracts\ResourceCatalogContribution;         // + resourceKey(), resourceModelClass()
use Nexia\Navigation\Contracts\NavigationContribution;                // + navigationItems()
use Nexia\Permission\Contracts\PermissionContribution;                // + catalogPermissionDefinitions()

그중 세 메서드는 손으로 쓰지 않고 SDK 트레이트가 공급합니다. ContributesResourcePermissions가 여러분의 permissionResources() 배열에서 catalogPermissionDefinitions()를 구현하고, ContributesNavigationDestination이 $navigation 배열에서 navigationItems()를 구현하고, HasShellResource가 Shell resource 면을 공급합니다. AbstractResourceModule::appDescriptors()는 resourceModuleDefinition()이 반환한 optional ResourceDescriptor를 단일 AppDescriptorContribution 계약으로 게시합니다. ResourceDescriptorContract는 Resource lifecycle event facet을 검증합니다.

최소 예시

Resource 만들기에서 생성한 Note 모델과 NoteStatus enum을 사용하는 완전한 예시입니다. 생성기의 선택적 Agent 내비게이션 액션을 제외하고 필수 인터페이스를 보여 줍니다. 실습에서는 생성된 module을 유지하고 workshop.note의 소유자를 중복 등록하지 마세요.

코드 예시
PHP
<?php

declare(strict_types=1);

namespace Amuzcorp\Nexia\Workshop\Contribution\Resources;

use Amuzcorp\Nexia\Workshop\Models\Note;
use Amuzcorp\Nexia\Workshop\Enums\NoteStatus;
use Nexia\AppDescriptors\DescriptorStatus;
use Nexia\AppDescriptors\ResourceDescriptor;
use Nexia\AppRuntime\Concerns\HasShellResource;
use Nexia\AppRuntime\Contracts\ShellResourceContribution;
use Nexia\Contribution\AbstractResourceModule;
use Nexia\Contribution\ResourceAuthorizationContract;
use Nexia\Contribution\Contracts\ResourceAuthorizationContribution;
use Nexia\Contribution\Contracts\ResourceCatalogContribution;
use Nexia\Contribution\ResourceLegalEntityParticipation;
use Nexia\Contribution\ResourceModuleDefinition;
use Nexia\Contribution\ResourceRecordOwner;
use Nexia\Navigation\Concerns\ContributesNavigationDestination;
use Nexia\Navigation\Contracts\NavigationContribution;
use Nexia\Permission\AssignmentScope;
use Nexia\Permission\Concerns\ContributesResourcePermissions;
use Nexia\Permission\Contracts\PermissionContribution;

final class NoteModule extends AbstractResourceModule implements NavigationContribution, PermissionContribution, ResourceAuthorizationContribution, ResourceCatalogContribution, ShellResourceContribution
{
    use ContributesNavigationDestination;
    use ContributesResourcePermissions;
    use HasShellResource;

    protected static array $navigation = [
        'path' => 'notes',
        'icon' => 'box',
        'order' => 50,
        'context' => 'app',
        'group' => 'operations',
        'visible' => true,
    ];

    public static function resourceKey(): string
    {
        return 'workshop.note';
    }

    public static function resourceModelClass(): string
    {
        return Note::class;
    }

    public static function resourceModuleDefinition(): ResourceModuleDefinition
    {
        return ResourceModuleDefinition::fromDescriptor(new ResourceDescriptor(
            key: self::resourceKey(),
            version: '1.0',
            status: DescriptorStatus::Active,
            labelKey: 'workshop.note.list.title',
            fieldSchema: [
                'public_id' => ['type' => 'string'],
                'name' => ['type' => 'string'],
                'status' => [
                    'type' => 'string',
                    'enum' => NoteStatus::values(),
                    'enum_labels' => NoteStatus::labelKeys(),
                ],
            ],
            searchSchema: ['label_fields' => ['name']],
        ));
    }

    public static function permissionAssignmentScope(): AssignmentScope
    {
        return AssignmentScope::LegalEntity;
    }

    public static function resourceAuthorization(): ResourceAuthorizationContract
    {
        return ResourceAuthorizationContract::standard(
            ResourceRecordOwner::LegalEntity,
            AssignmentScope::LegalEntity,
            ResourceLegalEntityParticipation::RecordOwner,
        );
    }

    public static function permissionResources(): array
    {
        return [
            'workshop.note' => ['read', 'create', 'update', 'delete'],
        ];
    }
}

ResourceModuleDefinition은 공용 이름의 기준이기도 합니다. public descriptor가 있으면 그 labelKey를 자동으로 사용합니다. 카탈로그 전용 Resource는 descriptor를 공개하지 않으면서도 화면과 독립적인 같은 이름을 선언할 수 있습니다.

코드 예시
PHP
return ResourceModuleDefinition::withoutDescriptor(
    self::resourceKey(),
    'workshop.note.resource.label',
);

목록 제목, 내비게이션 문구, 페이지 설명은 문맥에 따라 별도로 둘 수 있습니다. 어떤 화면이 Resource 자체의 이름을 필요로 할 때는 같은 문구의 화면 전용 별칭을 추가하지 말고 module label을 재사용하세요.

인자

인자타입필수기본값동작
packagestring예—기존 패키지 app key 또는 표시 이름. packages/{app_key}/composer.json에 대해 해석되고, app_key와 app_table_prefix는 extra.nexia.app에서 읽고 다시 파생하지 않습니다.
namestring예—PascalCase Resource 이름. 클래스 이름과 snake-case Resource Key 접미, 복수형 테이블 이름, 영어 라벨의 씨앗이 됩니다.

옵션

옵션기본값효과
--record-ownerlegal_entitytenant 또는 legal_entity. 테이블 모양과 API Route 접두, 권한 미들웨어, Policy 범위 검사, 목록 조건, 프런트엔드 요청 컨텍스트를 결정합니다. 다른 값은 받지 않습니다.
--navigation-groupoperationsinsights, management, operations, master-data, settings 중 하나. Shell이 이 다섯 그룹 라벨을 소유합니다.
--navigation-subgroup없음App이 소유하는 하위 그룹 id. 유효한 그룹이 필요합니다. 생략하면 목적지가 그룹 안에서 평평하게 유지됩니다.
--iconboxApp Menu 항목의 Shell 아이콘 이름. 알 수 없는 이름은 중립 아이콘으로 폴백합니다.
--sort다음 자리App Menu 순서. 낮으면 먼저 나옵니다. 첫 Resource는 10을 받습니다. --force에서는 --sort를 명시하지 않으면 기존 순서를 보존합니다.
--without-navigation꺼짐visible => false를 씁니다. Route와 권한, Catalog 메타데이터, Shell resource Route, Filament 등록은 전부 남고 App Menu 항목만 억제됩니다.
--label-ko, --label-ko-plural—한국어 단수는 필수이고 컬렉션 라벨은 단수를 기본으로 씁니다.
--label-zh, --label-zh-plural영어 라벨선택적 중국어 라벨입니다. 컬렉션 라벨은 중국어 단수를 기본으로 쓰며 둘 다 생략하면 파생된 영어 라벨을 명시적 fallback으로 기록합니다.
--dry-run꺼짐파일·편집 계획을 보고하고 아무것도 쓰지 않습니다.
--force꺼짐생성된 Resource 파일을 다시 씁니다. 패키지 안 Manifest·Route·프런트엔드 엔트리 편집은 멱등하게 유지됩니다. 레코드 소유자 변경은 허용하지 않습니다.

영어 라벨은 name에서 파생됩니다. 정확한 자동화 기본값이 없다면 한국어 단수 라벨은 필수이고 컬렉션 라벨은 이를 기본으로 씁니다. 중국어 라벨은 선택 사항이며 생략하면 파생된 영어 라벨을 명시적 fallback으로 씁니다. 한국어 값이 영어 counterpart와 같으면 nexia.translation_catalog.validation.identical_value_allowlist에 있지 않는 한 거부됩니다.

결과물

nexia-apps:make-package-resource workshop Note --record-owner=legal_entity --label-ko=노트 --label-ko-plural=노트에 대해, 경로는 모두 packages/workshop/ 기준입니다.

src/Enums/NoteStatus.php
src/Models/Note.php
src/Policies/NotePolicy.php
src/Http/Controllers/NoteController.php
src/Contribution/Resources/NoteModule.php
database/migrations/tenant/{timestamp}_create_wsp_notes_table.php
src/Filament/Tenant/Resources/Notes/NoteResource.php
src/Filament/Tenant/Resources/Notes/Pages/{ListNotes,ViewNote}.php
resources/js/resources/notes/note-resource-contract.ts
resources/js/resources/notes/note-information-schema.tsx
resources/js/resources/notes/surface/Note{List,Show,Form}Surface.tsx
resources/js/resources/notes/section/Note{List,Show,Form}Section.tsx
resources/js/resources/notes/inspector/{NoteInspector.tsx,register-note-inspector.ts}
tests/Feature/NoteAuthorizationTest.php

파일 스무 개입니다. 생성된 Show와 Form 섹션은 모두 note-information-schema.tsx를 투영하므로 필드 순서, 라벨, 필수성, 너비, 의도적인 View/Create/Edit 생략이 한 곳에서 정해집니다. 생성된 테스트는 Module이 선언한 소유권과 범위를 단정합니다. HTTP 요청을 하지 않으므로 엔드포인트 권한 테스트는 여러분이 추가할 몫입니다.

기존 파일 두 개를 수정하고 로케일 카탈로그에 키를 병합합니다. 엔트리의 glob 등록이 새 Inspector를 발견하므로 resources/js/index.ts에 import를 추가하지 않습니다.

파일편집
src/{App}AppManifest.php테넌트 Filament Resource 등록만
routes/routes.php컨트롤러 import와 정식 CRUD 경로. Legal Entity 소유권에는 명시적 대상 호환 경로도 추가

packages/{app_key}/ 밖으로는 아무것도 쓰지 않습니다. 호스트 routes/api/와 호스트 권한 프로바이더, 호스트 Shell 파일, 호스트 i18n 부트스트랩, 호스트 프런트엔드 별칭은 결코 편집되지 않습니다.

생성되는 API Route

레코드 소유자접두미들웨어 패턴
tenant/api/{app_key}/{resources}can.tenant_wide:{resource_key}.{action}
legal_entity/api/{app_key}/{resources}Controller가 목록·생성 요청의 대상 또는 저장된 레코드 소유자를 해석한 뒤 인가
legal_entity (호환)/api/legal-entities/{legalEntity:public_id}/{app_key}/{resources}can.scope:{resource_key}.{action},core.legal_entity,legalEntity

정식 접두마다 CRUD 경로 다섯 개를 제공합니다. Legal Entity Resource에는 명시적 대상 경로와의 호환을 위한 접두도 남습니다.

GET    {base}                      read
GET    {base}/{parameter}          read
POST   {base}                      create
PUT    {base}/{parameter}          update
DELETE {base}/{parameter}          delete

멤버 Route는 파라미터를 whereUuid로 제약하고 public_id를 해석합니다. 모든 Route가 추가로 app.installed:{app_key}를 담고, App과 그 전이적 전제가 코드로 로드되고 활성이고 현재 테넌트에 대해 성공적으로 초기화되지 않으면 닫힘 방향으로 실패합니다.

테이블·작명 계약

요소규칙예
테이블 이름{app_table_prefix}_{plural}wsp_notes
외래 키의미 중심, 접두 없음parent_note_id
Resource Key{app_key}.{resource_snake}workshop.note
번역 키{resource_key}.*workshop.note.status.active
프런트엔드 레지스트리 키App 접두를 단 PascalCaseWorkshopNoteListSurface
공개 식별자유일한 public_id UUIDpublic_id

생성된 {Resource}Status enum이 닫힌 값 집합의 유일한 출처입니다. 모델이 status를 그것으로 cast하고, 목록 필터가 filterOptions()에서 options를 파생하고, 컨트롤러가 Rule::enum()으로 검증하고, descriptor가 values()에서 fieldSchema의 enum을, labelKeys()에서 enum_labels를 파생합니다. 상태를 enum에 한 번 선언하면 넷이 모두 동기화됩니다.

Resource 목록 필드 카탈로그

모델은 백엔드 목록 쿼리 capability를 하나의 ResourceListFields 카탈로그로 선언합니다. 필드 key를 한 번 적고, 그 필드가 지원하는 검색·정렬·필터를 함께 붙입니다.

코드 예시
PHP
use Amuzcorp\Nexia\Workshop\Enums\NoteStatus;
use Nexia\Laravel\Filters\Types\Exact;
use Nexia\Laravel\Resources\ResourceListFields;

public function resourceListFields(): ResourceListFields
{
    return ResourceListFields::make()
        ->field(
            'status',
            sortable: true,
            filter: new Exact('status'),
            label: 'workshop.note.status.label',
            options: NoteStatus::filterOptions(),
        )
        ->field('name', searchable: true, sortable: true)
        ->field('updated_at', sortable: true);
}

Filterable은 이 카탈로그에서 요청 검증, 쿼리 적용, Scout 검색 projection, meta.list_schema를 파생합니다. 파생값이나 별칭을 정렬하려면 true 대신 Sort 인스턴스를 사용합니다. 필터 options는 closure로 지연 공급할 수 있고 프런트엔드 schema를 만들 때만 평가됩니다. 같은 필드 key를 다시 선언하면 거부됩니다.

이 카탈로그는 공개 ResourceDescriptor::$fieldSchema와 다릅니다. descriptor는 다른 플랫폼 capability에 Resource가 공개하는 내용을 설명하고, resourceListFields()는 컬렉션 쿼리 입력 allowlist입니다. 시각 컬럼이 쿼리 필드와 일대일로 대응하지 않을 수 있으므로 테이블 렌더링과 컬럼 표시 여부는 프런트엔드가 소유합니다.

수동 Resource Composition 카탈로그

Host는 Resource descriptor와 기존 authorization 계약에서 수동 Dashboard composition catalog를 파생합니다. Standard Resource는 직접 descriptor field를 공개하고, custom scope·redaction·projection Resource는 CompositionQueryContribution 하나를 바인딩할 수 있습니다. Reporting table이나 두 번째 graph 계약을 추가하지 마세요.

Resource는 다음 조건이 모두 유지될 때만 대상이 됩니다.

  • Descriptor가 active이고 번역되는 labelKey를 가지며 publicForBuilder: true를 유지합니다.
  • Descriptor와 Resource catalog의 owning App이 같고 그 App이 tenant에서 operational 상태입니다.
  • Resource가 지원되는 standard SQL authorization을 선언하거나, custom visibility에 필요한 authorized composition provider를 바인딩합니다.
  • Field schema에 직접 연결된 public_id identity와 composition에 안전하다고 명시한 field만 포함합니다.

Public catalog는 model, table, column, permission slug와 그 밖의 물리 binding을 제거합니다. public_id identity와 composition metadata가 해당 capability를 허용한 직접 descriptor field만 포함합니다. capability는 selectable, groupable, aggregation operation, time bucket입니다. 사용할 수 있는 출력 field에는 번역되는 label_key가 필요하고 enum choice는 enum_labels를 씁니다. resourceListFields()는 지원되는 Exact eq/in filter만 제공하며 field 값을 자동으로 반환하지 않습니다. Unsafe·derived·sensitive·actor·hash·token·내부 _id field는 계속 제외됩니다. Builder에 절대 보여서는 안 되는 Resource는 publicForBuilder: false로 설정하세요.

Authorized provider는 복원된 actor, request, Resource key와 정확한 Legal Entity/Operating Unit target pair를 받습니다. 이미 scope를 적용한 Eloquent builder와 Core가 사용할 alias·capability만 반환합니다. Core는 그 query를 감싸며 client SQL, 물리 column, 선언하지 않은 alias를 받지 않습니다. Provider는 Resource의 read permission과 row predicate를 재사용하며 Dashboard permission을 만들지 않습니다.

Relationship도 고정 App 쌍으로 선언하지 않고 파생합니다. 직접 resource_reference는 descriptor, 물리 field, 허용 target, target의 고유 public_id가 모두 일치할 때만 row relationship이 됩니다. 두 Resource가 각각 directory.party용 party_public_id를 선언하고 Core가 대응하는 party_id foreign-key topology를 증명할 수 있을 때만 Party 기준 aggregate relationship이 됩니다. 브라우저에는 불투명 relationship key와 의미적 endpoint만 전달됩니다.

Agent가 search나 resource_keys 없이 호출하면 인가된 전체 Resource의 간결한 색인만 받고 field와 관계 상세는 범위를 지정한 후속 요청에서 받습니다. 수동 Builder는 입력이 없는 경우에도 기존 전체 카탈로그 응답을 유지합니다.

App-neutral CompositionSpec wire는 하나의 현재 schema인 schema_version: 1을 사용합니다. 제한된 source alias 목록과 불투명 relationship을 전달하며, 각 relationship은 source, target, 명시적 match 동작을 함께 가집니다. aggregate에는 population이 필수이고 row 결과에는 row_source도 필수입니다. SDK는 wire와 선언한 alias를 검증하고, Core만 key를 해석하며 graph 의미·비용을 증명하고 live catalog 한도를 적용합니다. 한도는 source 8개, relationship/hop 7개, row 100개, aggregate bucket 500개, SQL timeout 3,000 ms입니다. App은 두 번째 composition graph 계약을 게시하지 않습니다. 선택 reporting metadata와 Report, Dashboard, Agent 사용 경계는 관리형 Report와 Resource Composition을 참조하세요.

Commit된 mutation identity

resourceKey()와 resourceModelClass()는 이 Resource를 호스트의 committed-mutation observer에도 등록합니다. 일반 Eloquent created, updated, deleted, restored event는 commit 뒤 안정된 Resource Key, operation, 문자열·정수 route key를 publish합니다. 생성된 controller와 stub에는 save/delete event 코드를 추가하지 않으며, 신호에는 model field가 없습니다.

이 자동 CRUD 관찰은 public lifecycle 선언이 아닙니다. 모든 ResourceLifecycleEventDescriptor와 payload schema는 ResourceDescriptor::$lifecycleEvents에 직접 선언하고, ResourceDescriptorContract가 그 public contract를 검증합니다. observer는 commit 뒤 catalog-bound model에 대해 generic MutationCollector::resourceChanged만 호출하며 lifecycle event나 payload를 만들어 내지 않습니다.

Query Builder bulk update, raw SQL 등 model event를 우회한 write는 자동 관찰되지 않습니다. 소유 service에 Nexia\Mutation\Contracts\MutationPublisher를 주입하고 write 성공 뒤 resourceChanged(resourceKey, operation, optionalResourceId)를 호출합니다. 업무 action에 Resource Catalog root가 없다면 안정된 owner 접두 action key를 actionSucceeded()로 publish합니다.

이 identity는 이미 대기 중인 브라우저 waiter가 authoritative state를 다시 읽을지 결정하게 할 뿐입니다. Durable event가 아니며 Setup criterion이나 업무 invariant 충족을 증명하지 않습니다. 모든 controller를 계측하거나 route를 match하거나 모든 성공 응답을 Resource 변경으로 취급하지 마세요.

권한 불변식

ResourceAuthorizationContract가 생성자에서 앞뒤가 맞지 않는 조합을 거부하므로, 잘못된 선언은 요청 시점이 아니라 부팅 때 실패합니다.

조합결과
Tenant + None이 아닌 participationInvalidArgumentException. 테넌트 소유 Resource는 직접 Legal Entity 소유자 participation을 쓸 수 없음
LegalEntity + RecordOwner가 아닌 participationInvalidArgumentException. 직접 Legal Entity 소유 Resource는 레코드 소유자를 Legal Entity participant로 지목해야 함

AssignmentScope는 Tenant(0)를 LegalEntity(1)보다, 그것을 OperatingUnit(2)보다 낮게 서열화합니다. supportsGrantAt()은 Resource 자신의 범위와 같거나 그보다 거친 Grant만 허용합니다.

가시성 규칙이 평범한 테넌트나 Legal Entity 소유가 아닐 때만 standard() 대신 ResourceAuthorizationContract::custom()을 쓰세요. ResourceVisibilityProfile::Custom을 선택하고 조건에 대한 책임을 여러분에게 넘깁니다.

오류

메시지원인해결
Package app not found at {dir}. Run nexia-apps:make-package-app first.해석된 경로에 패키지가 없음App Package 기준선을 먼저 만들기
Package routes must include [app.installed:{app_key}] before adding resources.Route 그룹에 설치 가드가 없음routes/routes.php의 그룹에 미들웨어 추가
App manifest [{class}] must end with AppManifest.Manifest 클래스 이름이 관례와 안 맞음Manifest 클래스와 extra.nexia.app.manifest 값 rename
Unknown --record-owner value. Supported values: tenant, legal_entity.organization을 포함한 다른 값tenant나 legal_entity 전달
A Korean resource label is required. Re-run with --label-ko=<label>.비대화형 실행에서 필수 한국어 단수 라벨 누락작성한 한국어 라벨 공급. 컬렉션 라벨은 이를 기본으로 쓰고 중국어는 선택 사항
Localized resource label --label-ko must not copy the English singular label [X].로케일 값이 영어 값과 같음실제 번역을 주거나 identical-value allowlist에 토큰 추가
Invalid navigation placement. Use group insights, management, operations, master-data, or settings and an optional subgroup.알 수 없는 그룹, 또는 유효한 그룹 없는 하위 그룹Shell이 소유하는 다섯 그룹 중 하나 쓰기
--icon must be a non-empty shell icon name.빈 --icon이름을 주거나 옵션 생략
--sort must be an integer.정수가 아닌 --sort정수 전달
Refusing to change record ownership from tenant to legal_entity through scaffold overwrite.기존 선언과 다른 --record-owner로 --force명시적 영속화·데이터 마이그레이션을 작성해 적용한 뒤 Module 선언과 강제 경로를 함께 갱신
Refusing to reinterpret an existing Resource that has no record-ownership declaration.기존 Module 파일에 ResourceRecordOwner:: 선언이 없음재생성 전에 선언을 명시적으로 추가
CONFLICT {path} (anchor '{anchor}' not found exactly once)생성기가 편집하는 패키지 안 파일이 알아볼 수 없을 만큼 손으로 수정됨앵커 주석을 복원하거나 편집을 수동으로 적용

App에 적용

Resource 만들기에서 Resource를 생성한 뒤 module, model, policy, controller, migration, 권한 선언 테스트를 함께 읽으세요. 경로와 이름은 Workshop에서 생성한 Note를 기준으로 합니다.

관련 문서

Resource 동작 메타데이터

ResourceDescriptor는 선택적인 actions (list<ResourceActionDescriptor>)와 mutation (ResourceMutationDescriptor)을 받습니다. 범용 플랫폼 기능이 사용할 기존 Resource API를 설명하는 계약입니다. 동작에는 정확한 경로, 권한, 입력 스키마, 읽기·변경 효과와 선택적인 라벨·업무 설명을 선언합니다. 변경 메타데이터는 생성과 수정 입력을 구분합니다. 이 descriptor는 개별 Agent 도구를 등록하거나 컨트롤러 검증을 대체하지 않습니다. Agent Resource Data 런타임을 참고하세요.

원본 위치: docs/developers/content/ko/app-sdk/resource-contracts.md