{"_id":"@dongkseo/tenancy","_rev":"4-44ff2a83fb1941ec8e8a5968cfb80829","name":"@dongkseo/tenancy","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.0":{"name":"@dongkseo/tenancy","version":"0.1.0","_id":"@dongkseo/tenancy@0.1.0","maintainers":[{"name":"dongkseo","email":"ssddgg99@daum.net"}],"dist":{"shasum":"ba820b45ac36fb01f0d12003ab36a0fff9b89bd7","tarball":"https://registry.npmjs.org/@dongkseo/tenancy/-/tenancy-0.1.0.tgz","fileCount":13,"integrity":"sha512-8X/dgmSZMs015S/r166/12vyjNjw8ihUFQNLr3Hd5eqJ20tNhqJ8CokNrnMelLr8fDzJNlZuyLn+Ldret5xC2A==","signatures":[{"sig":"MEUCIQCFHSvC2ii0YEu9H0M88rxfpBHa4sTjkeEyXi1mN1rzbwIgInuQLWBbAWWMO4AUMiAK7T1XJTnE+yQHBYbYYK3HeJ4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":8190},"main":"./dist/index.js","type":"module","_from":"file:dongkseo-tenancy-0.1.0.tgz","types":"./dist/index.d.ts","nexora":{"stability":"experimental"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsc","clean":"rm -rf dist .tsbuildinfo"},"_npmUser":{"name":"dongkseo","email":"ssddgg99@daum.net"},"_resolved":"/private/var/folders/f6/dfc3mm717vx9xjh0vbgqnddr0000gn/T/e0e230ccc838a3fd1b683b4173bf64f0/dongkseo-tenancy-0.1.0.tgz","_integrity":"sha512-8X/dgmSZMs015S/r166/12vyjNjw8ihUFQNLr3Hd5eqJ20tNhqJ8CokNrnMelLr8fDzJNlZuyLn+Ldret5xC2A==","_npmVersion":"11.12.1","directories":{},"_nodeVersion":"26.0.0","dependencies":{"@dongkseo/context":"0.1.1","@dongkseo/contracts":"0.1.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.0","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/tenancy_0.1.0_1781856058456_0.3092770393334616","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@dongkseo/tenancy","version":"0.1.1","_id":"@dongkseo/tenancy@0.1.1","maintainers":[{"name":"dongkseo","email":"ssddgg99@daum.net"}],"dist":{"shasum":"d1aab221077d365915082f2d29869316a43a329d","tarball":"https://registry.npmjs.org/@dongkseo/tenancy/-/tenancy-0.1.1.tgz","fileCount":13,"integrity":"sha512-Z5YiRns7c3JWF31ayIifvj3D6MRxmiTZiqURddlwTanJdZxwE6CIIm57gJvwz+cO3Jzuo4BS13CZ8PqeMmZjQw==","signatures":[{"sig":"MEQCIFNZHfipz8JQ/1qh2c4IOzrYCCblb5zJritO7cymdtWXAiBK7yIu3+lSFfwTOBLa1/wQK3J57VmOrd9iuzZdNpS6ag==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":8192},"main":"./dist/index.js","type":"module","_from":"file:dongkseo-tenancy-0.1.1.tgz","types":"./dist/index.d.ts","nexora":{"stability":"experimental"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsc","clean":"rm -rf dist .tsbuildinfo"},"_npmUser":{"name":"dongkseo","email":"ssddgg99@daum.net"},"_resolved":"/private/var/folders/f6/dfc3mm717vx9xjh0vbgqnddr0000gn/T/671a09a4c6ac9a879070f3d2653dfd7a/dongkseo-tenancy-0.1.1.tgz","_integrity":"sha512-Z5YiRns7c3JWF31ayIifvj3D6MRxmiTZiqURddlwTanJdZxwE6CIIm57gJvwz+cO3Jzuo4BS13CZ8PqeMmZjQw==","_npmVersion":"11.12.1","directories":{},"_nodeVersion":"26.0.0","dependencies":{"@dongkseo/context":"^0.1.2","@dongkseo/contracts":"^0.1.8"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.0","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/tenancy_0.1.1_1782200192542_0.14115603638131158","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@dongkseo/tenancy","version":"0.1.2","_id":"@dongkseo/tenancy@0.1.2","maintainers":[{"name":"dongkseo","email":"ssddgg99@daum.net"}],"dist":{"shasum":"37d41a7f1726f89032f8ca2e45dbc30f91be85fe","tarball":"https://registry.npmjs.org/@dongkseo/tenancy/-/tenancy-0.1.2.tgz","fileCount":14,"integrity":"sha512-xw0hLpjQsePbGaKlr1Fx/1NVpWhogHLMc8YuztwFY4/n6vMV7nD+byPiGVkQysuEIJnhvtYGvCTyn+AqAG4RDg==","signatures":[{"sig":"MEUCIQD2yQPFaLiIDfBdBBDPwiR6Uv8Xj3Bca61keRBLYGpEKwIgd3ONqp5gtYyt8eKMQEPNhkihc080sY7EcUlGmj/9jN4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":13669},"main":"./dist/index.js","type":"module","_from":"file:dongkseo-tenancy-0.1.2.tgz","types":"./dist/index.d.ts","nexora":{"stability":"experimental"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsc","clean":"rm -rf dist .tsbuildinfo"},"_npmUser":{"name":"dongkseo","email":"ssddgg99@daum.net"},"_resolved":"/private/var/folders/lw/t8yfg__j2nxd25m53gt8840h0000gn/T/3bedb59ee65aa77f05050e38d2435956/dongkseo-tenancy-0.1.2.tgz","_integrity":"sha512-xw0hLpjQsePbGaKlr1Fx/1NVpWhogHLMc8YuztwFY4/n6vMV7nD+byPiGVkQysuEIJnhvtYGvCTyn+AqAG4RDg==","_npmVersion":"11.16.0","description":"**Stability: experimental** · `pnpm add @dongkseo/tenancy`","directories":{},"_nodeVersion":"26.3.1","dependencies":{"@dongkseo/context":"^0.1.3","@dongkseo/contracts":"^0.1.11"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.0","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/tenancy_0.1.2_1782389358771_0.04632393112203825","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@dongkseo/tenancy","version":"0.1.3","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"dependencies":{"@dongkseo/contracts":"^0.1.32","@dongkseo/context":"^0.1.4"},"devDependencies":{"@types/node":"^26.1.2","typescript":"^7.0.2"},"nexora":{"stability":"experimental"},"publishConfig":{"access":"public"},"scripts":{"build":"tsc","test":"vitest run","clean":"rm -rf dist .tsbuildinfo"},"_nodeVersion":"26.7.0","_id":"@dongkseo/tenancy@0.1.3","dist":{"integrity":"sha512-BJTOb1avcaBnhvyD1AR4nD94QoSCvypkgEZjQTcRbSH7UYEnpv48Iw+OSBPPcYSHaqCyCPnTk1toowZDl5k1ug==","shasum":"d36bc150dae63d29a304dbe593e94bf17e1db03e","tarball":"https://registry.npmjs.org/@dongkseo/tenancy/-/tenancy-0.1.3.tgz","fileCount":14,"unpackedSize":13732,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD3wKhkVCr1QBNnZ6YhAwSR3Ur8af8BwOm4OkuisY3RGQIhAJW4VDQIoF8E0nwigczl0S8D7epjT8qG6TnSaF6dJHSe"}]},"_npmUser":{"name":"dongkseo","email":"ssddgg99@daum.net"},"directories":{},"maintainers":[{"name":"dongkseo","email":"ssddgg99@daum.net"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/tenancy_0.1.3_1786622545276_0.8882850224634438"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-19T08:00:58.243Z","modified":"2026-08-13T12:02:25.553Z","0.1.0":"2026-06-19T08:00:58.584Z","0.1.1":"2026-06-23T07:36:32.696Z","0.1.2":"2026-06-25T12:09:18.896Z","0.1.3":"2026-08-13T12:02:25.422Z"},"maintainers":[{"name":"dongkseo","email":"ssddgg99@daum.net"}],"readme":"# @dongkseo/tenancy\n\n**Stability: experimental** · `pnpm add @dongkseo/tenancy`\n\n> 이 파일은 에이전트(사람·LLM)가 **소스를 열지 않고도** 이 패키지를 쓸 수 있게 하는 오리엔테이션 문서다.\n> 정확한 API 타입은 \"API 표면\" 섹션의 방법으로 `signatures`만 읽어라 — 본문을 베끼지 말 것.\n\n## 무엇인가 / 무엇이 아닌가\n\nNexora의 **opt-in 멀티테넌시** 패키지다. 프레임워크 코어는 테넌트를 모른다 — 테넌트 id가 없으면\n모든 것이 단일 테넌트(`DEFAULT_TENANT`)로 동작하고, 어떤 코드도 테넌트 개념을 들고 다니지 않는다.\n하나의 배포 뒤에서 여러 테넌트를 격리해야 할 때만 이 패키지를 import 한다. 그러면 그 격리에 필요한\n**리졸버 + 테넌트별 config + 예산 스코프**가 한 곳에서 묶여 온다.\n\n- ✅ 담는 것: HTTP 요청에서 테넌트를 뽑는 리졸버(`headerTenantResolver`), 테넌트 예산 스코프 팩토리\n  (`tenantBudgetScope`, `tenantAgentBudgetScope`), 그리고 테넌트별 config/스코프 진입점 re-export\n- ❌ 안 담는 것: 실행 엔진, 예산 집행 로직, ContextLoader 구현, LLM 호출 — 그건 `core`/`context` 몫\n- 의존 방향: **app → tenancy → contracts/context** 단방향. 코어는 tenancy를 모른다(반대 방향 의존 없음).\n\n설계 배경은 `docs/architecture/adrs/adr-001-tenancy-opt-in.md` 참고.\n\n## 핵심 개념\n\n| 개념 | 무엇 | 대표 export |\n| --- | --- | --- |\n| **Tenant resolver** | 인바운드 HTTP 요청에서 테넌트 id를 뽑는 함수 (어댑터의 `resolveTenant`에 꽂음) | `headerTenantResolver`, `TenantResolver` |\n| **Budget scope** | 예산 정책이 적용될 범위(테넌트 / 테넌트+에이전트) 식별자 | `tenantBudgetScope`, `tenantAgentBudgetScope`, `BudgetScope` |\n| **Tenant config** | 테넌트별 페르소나·limits·도구 화이트리스트 저장소 (ContextLoader가 소비) | `TenantConfigStore`, `TenantConfig`, `DEFAULT_LIMITS` |\n| **Default tenant** | tenancy를 안 깔았을 때 쓰이는 단일 테넌트 id | `DEFAULT_TENANT` |\n\n> `TenantConfigStore`/`DEFAULT_LIMITS`/`DEFAULT_TENANT`/`BudgetScope`는 사이클을 피하려고 물리적으로\n> `@dongkseo/context`·`@dongkseo/contracts`에 있지만, **tenancy가 단일 opt-in 진입점**이 되도록 여기서 re-export 한다.\n\n## 사용 레시피\n\nHTTP 어댑터에 테넌트 리졸버와 테넌트 config를 꽂아 멀티테넌시를 켠다 (소스 TSDoc 기준, 실제 동작 코드):\n\n```ts\nimport { headerTenantResolver, TenantConfigStore } from '@dongkseo/tenancy';\n\nconst http = new HttpAdapter({ resolveTenant: headerTenantResolver('x-tenant-id') });\nconst contextLoader = new CoreContextLoader({ tenants: new TenantConfigStore({ root }) });\n```\n\n헤더가 없거나 비어 있으면 `DEFAULT_TENANT`로 폴백한다. 커스텀 헤더명도 받는다:\n\n```ts\nimport { headerTenantResolver } from '@dongkseo/tenancy';\n\nconst resolve = headerTenantResolver('x-org');\nresolve(req); // req.headers['x-org'] 값, 없으면 DEFAULT_TENANT\n```\n\n예산 정책에 넘길 스코프를 만든다:\n\n```ts\nimport { tenantBudgetScope, tenantAgentBudgetScope } from '@dongkseo/tenancy';\n\ntenantBudgetScope('acme');                 // { type: 'tenant', tenantId: 'acme' }\ntenantAgentBudgetScope('acme', 'coder');   // { type: 'tenant-agent', tenantId: 'acme', agentName: 'coder' }\n```\n\n## API 표면 (소스 안 열고 타입만)\n\n`index.ts` 맨 위에 **섹션 맵 주석**이 있어 어떤 export가 어느 파일에서 오는지 보인다. 정확한 시그니처가\n필요하면 구현 본문 대신 **signatures 모드**로만 읽어라:\n\n```\nctx_read(path=\"packages/tenancy/src/resolver.ts\", mode=\"signatures\")\nctx_read(path=\"packages/tenancy/src/budget.ts\",   mode=\"signatures\")\nctx_read(path=\"packages/tenancy/src/index.ts\",    mode=\"map\")   # 전체 export 목록 + 섹션 맵\n```\n\nre-export된 `TenantConfigStore`/`TenantConfig`/`DEFAULT_LIMITS`의 정본은 `@dongkseo/context`의 `src/tenant.ts`,\n`BudgetScope`/`DEFAULT_TENANT`는 `@dongkseo/contracts`에 있다.\n\n## 유지보수 (drift 방지)\n\n- 이 README = **목적·개념·레시피** 만. 거의 안 변하므로 손으로 관리.\n- **API 정본은 각 소스의 TSDoc** (`/** … */`). API가 바뀌면 코드 옆 TSDoc만 고치면 됨 — 여기 표는 복제하지 말 것.\n- 새 모듈을 export하면 `index.ts` 상단 섹션 맵에 한 줄만 추가.\n\n## Tests\n\n```bash\ncd packages/tenancy && pnpm test\n```\n\nPart of the [Nexora](../../README.md) multi-tenant agent framework. · [Package map](../../docs/architecture/packages-map.md)\n","readmeFilename":""}