GNL
Dokümanlar menüsü
Enterprise@gnldev/server

Çok organizasyonluluk (organizasyon izolasyonu)

Her isteği bir organizasyonun izole journal'ına indirir (withOrg); run'lar, memory ve exactly-once garantileri organizasyon başına ayrışır, kimliğe bağlı organizasyon header'ı ezer (uyuşmazlıkta 403).

Ne işe yarar / ne zaman kullanılır#

Tek bir GNL dağıtımıyla birden çok müşteriye (organizasyona) hizmet vermek istediğinde — ör. bir SaaS ürününde her müşterinin run geçmişi, memory'si ve kullanım/bütçe sayaçları diğerlerinden tamamen ayrı olmalı. org opsiyonu açıldığında her istek, withOrg ile kapsamlanmış ayrı bir journal'a iner; organizasyonlar birbirinin anahtarlarını göremez, aynı runId farklı organizasyonlarda bağımsızdır.

Kimliğe bağlı organizasyon (Cred.orgId ile verilmiş, ör. bir viewer token'ı) x-gnl-org header'ını her zaman ezer: o kimlik başka bir organizasyon istese bile 403 döner — böylece bir token asla kendi organizasyonu dışına sızamaz.

Kurulum / import#

org ve OrgOptions @gnldev/server'ın createRestApi seçenekleridir; withOrg alttan @gnldev/durable'dan gelir. Kimliğe bağlı organizasyon (Cred.orgId) için EE auth (@gnldev/auth-ee) veya ücretsiz roleAuth'un orgId alanı kullanılır.

npm install
npm install @gnldev/server @gnldev/durable  # @gnldev/auth-ee arrives separately, under license
config + auth
import { SqliteStorage } from '@gnldev/durable/sqlite';
import { createRestApi } from '@gnldev/server';
import { createEnterpriseAuth } from '@gnldev/auth-ee';
import { roleAuth } from '@gnldev/auth';

const storage = new SqliteStorage('runs.db');
const config = { storage, agents: { starwars: { model, system: SYSTEM, tools } } };

// The 'acme-viewer' token carries an IDENTITY-BOUND organization (orgId) → it only ever sees
// acme's data; asking for another org returns 403. admin is global and can act in any
// organization via the x-gnl-org header.
const auth = createEnterpriseAuth({
  licenseKey: process.env.GNL_LICENSE_KEY,
  publicKey: process.env.GNL_EE_PUBLIC_KEY,
  failClosed: true,
  fallback: roleAuth({
    admin: { token: ADMIN_TOKEN, user: 'ops' },
    viewer: { token: ACME_TOKEN, orgId: 'acme' }, // demo viewer bound to an organization
  }),
});

Adım adım kullanım#

createRestApi'ye org: {} vermek yeterli — organizasyon varsayılan olarak x-gnl-org header'ından çözülür (OrgOptions.resolve ile özelleştirilebilir). Organizasyon başına registry lazy kurulur ve önbelleklenir.

org izolasyonunu aç
app.route('/api', createRestApi(config, {
  title: 'SWAPI Pro',
  auth,
  org: {},       // opt-in: every request lands in its organization's journal (withOrg)
  budgets: {},   // optional — per-organization budget/quota (separate doc: budget-quota)
}));

Global (bootstrap) kimlik, header ile istediği organizasyonda çalışabilir:

admin: x-gnl-org ile organizasyon seç
# admin: acts in any organization via the x-gnl-org header
curl -s -X POST http://localhost:3002/api/agents/starwars/run \
  -H 'content-type: application/json' -H 'authorization: Bearer ${ADMIN_TOKEN}' -H 'x-gnl-org: acme' \
  -d '{"runId":"demo-1","prompt":"Who is Darth Vader?"}'

Organizasyona bağlı bir kimlik (viewer) header vermese bile kendi organizasyonuna düşer; farklı bir organizasyon istemeye çalışırsa 403 alır:

organizasyona bağlı viewer + uyuşmazlık 403
# organization-BOUND viewer: scoped to acme even without the header
curl -s http://localhost:3002/api/usage -H 'authorization: Bearer ${ACME_TOKEN}'

# the same viewer asking for another organization (x-gnl-org: globex) → 403 org mismatch
curl -s http://localhost:3002/api/runs -H 'authorization: Bearer ${ACME_TOKEN}' -H 'x-gnl-org: globex'
# -> 403 "organization mismatch: identity is bound to organization 'acme'"

Altta yatan izolasyon mekanizması doğrudan da kullanılabilir — journal'ı elle bir organizasyona kapsamla:

withOrg — doğrudan kullanım
import { withOrg } from '@gnldev/durable';

// createRestApi does this AUTOMATICALLY with opt-in org, but you can also use it directly:
// EVERY key in the journal is written/read under an `org:<orgId>:` prefix runs, memory,
// queue and cache included, so EVERYTHING is isolated (same Journal interface; exactly-once
// is inherited for free).
const scoped = withOrg(baseJournal, 'acme');
// scoped.listRuns() returns ONLY acme's runs (with the prefix stripped).

API referansı#

fnwithOrg

Bir journal'ı organizasyona kapsamlar (@gnldev/durable): dönen journal aynı arayüzü uygular (get/put/putIfAbsent/listKeys/deletePrefix/readRun/listRuns varsa köprülenir), tüm anahtarlar `org:<orgId>:` önekiyle taşınır ve okurken önek soyulur.

typeRestApiOptions.org

createRestApi seçeneği (@gnldev/server): opt-in çok organizasyonluluk — verilirse her istek withOrg ile kapsamlanmış journal görür, çözülen organizasyon requestContext'e enjekte edilir.

typeOrgOptions

{ resolve?: (c) => orgId, required?: boolean } — resolve verilmezse x-gnl-org header'ı okunur; required true ise organizasyonsuz istek 400 alır (false ise paylaşılan alanda çalışır).

fnprincipalOf

@gnldev/auth: Hono Context'inden doğrulanmış Principal'ı okur; Principal.orgId burada gelir ve scope() içinde bağlı organizasyonu belirler.

typeCred.orgId

roleAuth (@gnldev/auth) kimlik bilgisinde (Cred) opsiyonel alan: verilirse o rolün kimliği bir organizasyona bağlanır — istek header'ından bağımsız olarak her zaman o organizasyona kapsamlanır, farklı organizasyon istenirse 403.

Lisans gerektirir
Bu özellik Enterprise (ee) katmandadır — @gnldev/auth-ee ile imzalı lisans şarttır (bkz. İmzalı lisans). Kimliğe bağlı organizasyon için EE auth zorunlu değildir; ücretsiz roleAuth'un Cred.orgId alanıyla da bağlanabilir — ama org seçeneğinin kendisi @gnldev/server'da serbesttir, paralı olan tam kimlik/rol/lisans zinciridir.
İpucu
Organizasyon başına registry (model-fallback, memory dahil) lazy kurulur ve önbelleklenir — her istek yeni bir createGnl çağrısı yapmaz. Bütçe/kota da organizasyon bazında ayrışır; ayrıntı için Bütçe & kota dokümanına bakın.