Ders 03 / 21
Kimlik ve Kaynak Temelli Politikalar
Aynı erişim isteği iki ayrı politika kümesinden geçirilir ve kesişimi hesaplanır: kaç istek yalnız bir yönde izinli olduğu için reddediliyor, hesap sınırı aşıldığında kaynak temelli politikanın neden zorunlu olduğu ve bir retin nedenini bulmanın kaç belge okumayı gerektirdiği sayılır.
İçindekiler
Önceki dersin beş belgesi tek bir yerde duruyordu ve hepsi bir özneye bağlıydı. Kaynağın kendisi de politika taşıyabilir: deponun üstüne yazılan bir belge, ona kimin erişebileceğini kaynak tarafından söyler. İki yön aynı soruya iki ayrı yerden cevap verir — biri “bu özne nereye erişebilir”, öbürü “bu kaynağa kimler erişebilir” — ve bir istek ikisinden birden geçer.
İki yönün var olma nedeni farklıdır. Kimlik temelli belge özneyi yöneten ekibin elindedir, kaynak temelli belge kaynağı işletenin. Aynı hesabın içinde ikisi birbirinin yerine geçebilir, hesap sınırı aşıldığında geçemez. Bu dersin ölçüsü kesişimin boyu ve o kesişimi okumanın maliyetidir.
- BI13. Model sekiz özne, beş fiil, yirmi bir kaynak ve üç hesap taşır. Gerçek bir hesap
koşturulmuyor; ölçülen şey
nodeile kurulan modeldir. - BI14. Değerlendirme kuralı ders içinde tanımlıdır: aynı hesapta bir yönün izin vermesi yeterli, hesap sınırı aşıldığında iki yönün de izin vermesi gerekli, açık ret her iki yönde de kazanır.
- BI15. On bir kimlik temelli, altı kaynak temelli belge elle yazıldı. Sayılar bu kümeye bağlıdır; ölçüm biçimi değildir.
- BI16. Otuz günlük kullanım izi elle yazıldı ve ortam ayrımı taşımaz — izde görünen bir üçlü üç ortamda da meşru sayılır. Kullanılmayan izin sayısı bu yüzden bir alt sınırdır.
- BI17. “Bakılacak belge” sayısı özneye bağlı belge, kaynağa uyan belge ve hesap kuralının toplamıdır. Gerçekte devralınan belgeler sayıyı büyütür; ölçülen değer alt sınırdır.
- BI18. Kuruluş yapısı üç hesaba indirgendi; hesapların birbirini kapsaması modele girmedi.
İki Yön
// yon.mjs — iki degerlendirme yonu; gercek bir hesap degil, node ile kurulan modeldir export const FIIL = ['oku', 'listele', 'yaz', 'sil', 'yapilandir']; const ORTAM = ['gelistirme', 'hazirlik', 'uretim']; export const TUR_HESAP = { // kaynak turu hangi hesapta duruyor 'olcum-deposu': 'hesap-olcum', 'kayit-akisi': 'hesap-olcum', 'kenar-dugum-grubu': 'hesap-olcum', 'nesne-deposu': 'hesap-analitik', 'islev-paketi': 'hesap-analitik', 'makine-kumesi': 'hesap-ortak', 'ag-yapilandirmasi': 'hesap-ortak', }; export const OZNE_HESAP = { 'toplama-hizmeti': 'hesap-olcum', 'dogrulama-hizmeti': 'hesap-olcum', 'kenar-toplayici': 'hesap-olcum', 'analitik-is': 'hesap-analitik', 'pano-okuyucu': 'hesap-analitik', 'faturalama-is': 'hesap-ortak', 'saha-bakim': 'hesap-ortak', 'yedek-dogrulama': 'hesap-ortak', }; export const OZNE = Object.keys(OZNE_HESAP); export const KAYNAK = Object.keys(TUR_HESAP).flatMap((t) => ORTAM.map((o) => ({ ad: `${t}/${o}`, tur: t, hesap: TUR_HESAP[t] }))); export const ISTEK = OZNE.flatMap((o) => FIIL.flatMap((f) => KAYNAK.map((k) => ({ ozne: o, fiil: f, kaynak: k })))); const OKU = ['oku', 'listele'], OKU_YAZ = ['oku', 'listele', 'yaz']; const K = (ad, ozne, etki, fiil, kaynak) => ({ ad, ozne, etki, fiil, kaynak }); const R = (ad, kaynak, etki, ozne, fiil) => ({ ad, kaynak, etki, ozne, fiil }); export const KIMLIK_BELGE = [ // ozneye bagli: nerede ne yapabilir K('k1', 'toplama-hizmeti', 'izin', OKU_YAZ, ['olcum-deposu/*', 'kayit-akisi/*']), K('k2', 'toplama-hizmeti', 'ret', ['sil'], ['*']), K('k3', 'dogrulama-hizmeti', 'izin', OKU, ['*']), K('k4', 'kenar-toplayici', 'izin', OKU_YAZ, ['kenar-dugum-grubu/*', 'kayit-akisi/*']), K('k5', 'analitik-is', 'izin', OKU, ['olcum-deposu/*', 'nesne-deposu/*']), K('k6', 'analitik-is', 'izin', ['yaz'], ['nesne-deposu/hazirlik', 'nesne-deposu/gelistirme']), K('k7', 'pano-okuyucu', 'izin', OKU, ['olcum-deposu/*', 'kayit-akisi/*']), K('k8', 'faturalama-is', 'izin', OKU, ['olcum-deposu/*', 'nesne-deposu/*', 'kayit-akisi/*']), K('k9', 'saha-bakim', 'izin', [...OKU_YAZ, 'yapilandir'], ['makine-kumesi/*', 'ag-yapilandirmasi/*', 'kenar-dugum-grubu/*']), K('k10', 'saha-bakim', 'ret', ['sil'], ['*/uretim']), K('k11', 'yedek-dogrulama', 'izin', OKU, ['nesne-deposu/*', 'olcum-deposu/*']), ]; export const KAYNAK_BELGE = [ // kaynaga bagli: kim ne yapabilir R('r1', ['olcum-deposu/*'], 'izin', ['analitik-is', 'faturalama-is'], OKU), R('r2', ['nesne-deposu/*'], 'izin', ['*'], OKU), R('r3', ['kayit-akisi/*'], 'izin', ['toplama-hizmeti', 'kenar-toplayici'], OKU_YAZ), R('r4', ['makine-kumesi/*', 'ag-yapilandirmasi/*'], 'izin', ['saha-bakim'], [...OKU_YAZ, 'yapilandir']), R('r5', ['*/uretim'], 'ret', ['pano-okuyucu'], ['yaz', 'sil', 'yapilandir']), R('r6', ['kenar-dugum-grubu/*'], 'izin', ['kenar-toplayici', 'saha-bakim'], OKU_YAZ), ]; // Otuz gunluk kullanim izi: (ozne, fiil, kaynak turu); elle yazildi, ortam ayrimi tasimaz export const IZ = new Set([ ['toplama-hizmeti', OKU_YAZ, ['olcum-deposu', 'kayit-akisi']], ['dogrulama-hizmeti', ['oku'], ['olcum-deposu', 'kayit-akisi', 'makine-kumesi']], ['kenar-toplayici', ['oku', 'yaz'], ['kenar-dugum-grubu', 'kayit-akisi']], ['analitik-is', OKU, ['olcum-deposu', 'nesne-deposu']], ['pano-okuyucu', OKU, ['olcum-deposu', 'kayit-akisi']], ['faturalama-is', ['oku'], ['olcum-deposu', 'nesne-deposu', 'kayit-akisi']], ['saha-bakim', ['oku', 'yaz', 'yapilandir'], ['makine-kumesi', 'ag-yapilandirmasi']], ['yedek-dogrulama', ['oku'], ['nesne-deposu', 'olcum-deposu']], ].flatMap(([o, fs, ts]) => fs.flatMap((f) => ts.map((t) => `${o}|${f}|${t}`)))); export const izde = (i) => IZ.has(`${i.ozne}|${i.fiil}|${i.kaynak.tur}`); export const desen = (p, s) => new RegExp(`^${p.split('*').map((x) => x.replace(/[.+?^${}()|[\]\\]/g, '\\$&')).join('.*')}$`).test(s); export const kimlikBelgeleri = (i, kb = KIMLIK_BELGE) => kb.filter((b) => b.ozne === i.ozne); export const kaynakBelgeleri = (i, rb = KAYNAK_BELGE) => rb.filter((b) => b.kaynak.some((p) => desen(p, i.kaynak.ad))); const uyar = (b, i) => b.fiil.includes(i.fiil) && b.kaynak.some((p) => desen(p, i.kaynak.ad)) && (typeof b.ozne === 'string' ? b.ozne === i.ozne : b.ozne.includes('*') || b.ozne.includes(i.ozne)); export function yon(belgeler, i) { // bir yonun sonucu const u = belgeler.filter((b) => uyar(b, i)); if (u.some((b) => b.etki === 'ret')) return 'acik-ret'; return u.some((b) => b.etki === 'izin') ? 'izin' : 'yok'; } // Model kurali: ayni hesapta bir yon yeter, hesap sinirinda iki yon de izin vermeli export function degerlendir(i, kb = KIMLIK_BELGE, rb = KAYNAK_BELGE) { const a = yon(kimlikBelgeleri(i, kb), i), b = yon(kaynakBelgeleri(i, rb), i); if (a === 'acik-ret' || b === 'acik-ret') return 'acik-ret'; const ayni = OZNE_HESAP[i.ozne] === i.kaynak.hesap; return (ayni ? a === 'izin' || b === 'izin' : a === 'izin' && b === 'izin') ? 'izin' : 'ortuk-ret'; }
İki belge türünün alanları aynı, taşıdıkları liste farklı: kimlik temelli belge bir özneye bağlıdır
ve kaynak deseni taşır, kaynak temelli belge bir kaynağa bağlıdır ve özne listesi taşır. r2
belgesindeki * özne listesi bu farkın en görünür yeri — kaynak, kendisine kimin erişebileceğini
adıyla saymak yerine hepsini açabiliyor.
Kesişim
// kosum1.mjs — ayni istek iki yonden gecirilir ve kesisim hesaplanir import { ISTEK, OZNE, KAYNAK, FIIL, OZNE_HESAP, KIMLIK_BELGE, KAYNAK_BELGE, kimlikBelgeleri, kaynakBelgeleri, yon, degerlendir } from './yon.mjs'; const ayniHesap = (i) => OZNE_HESAP[i.ozne] === i.kaynak.hesap; const yonler = ISTEK.map((i) => ({ i, a: yon(kimlikBelgeleri(i), i), b: yon(kaynakBelgeleri(i), i), ayni: ayniHesap(i), sonuc: degerlendir(i) })); console.log(`${OZNE.length} ozne x ${FIIL.length} fiil x ${KAYNAK.length} kaynak = ${ISTEK.length} istek`); console.log(`kimlik temelli belge ${KIMLIK_BELGE.length}, kaynak temelli belge ${KAYNAK_BELGE.length}`); console.log(`ayni hesap ${yonler.filter((y) => y.ayni).length}, hesap sinirini asan ${yonler.filter((y) => !y.ayni).length}\n`); const say = (f) => yonler.filter(f).length; console.log('iki yonun kesisimi ayni hesap hesap siniri izinli sonuc'); const satir = (ad, f) => console.log(`${ad.padEnd(25)} ${String(say((y) => y.ayni && f(y))).padEnd(11)} ` + `${String(say((y) => !y.ayni && f(y))).padEnd(13)} ${say((y) => f(y) && y.sonuc === 'izin')}`); satir('yalniz kimlik yonu izin', (y) => y.a === 'izin' && y.b !== 'izin' && y.b !== 'acik-ret'); satir('yalniz kaynak yonu izin', (y) => y.b === 'izin' && y.a !== 'izin' && y.a !== 'acik-ret'); satir('iki yon de izin', (y) => y.a === 'izin' && y.b === 'izin'); satir('hicbir yon izin vermiyor', (y) => y.a !== 'izin' && y.b !== 'izin'); satir('en az bir yonde acik ret', (y) => y.a === 'acik-ret' || y.b === 'acik-ret'); const s = { izin: 0, 'acik-ret': 0, 'ortuk-ret': 0 }; for (const y of yonler) s[y.sonuc]++; console.log(`\nsonuc: izin ${s.izin}, acik ret ${s['acik-ret']}, ortuk ret ${s['ortuk-ret']}`); const tekYonYeterdi = say((y) => y.sonuc !== 'izin' && (y.a === 'izin' || y.b === 'izin') && y.a !== 'acik-ret' && y.b !== 'acik-ret'); console.log(`bir yonde izinli oldugu halde reddedilen: ${tekYonYeterdi} istek — hepsi hesap sinirini asiyor`);
8 ozne x 5 fiil x 21 kaynak = 840 istek kimlik temelli belge 11, kaynak temelli belge 6 ayni hesap 285, hesap sinirini asan 555 iki yonun kesisimi ayni hesap hesap siniri izinli sonuc yalniz kimlik yonu izin 29 45 29 yalniz kaynak yonu izin 6 18 6 iki yon de izin 57 39 96 hicbir yon izin vermiyor 193 453 0 en az bir yonde acik ret 17 32 0 sonuc: izin 131, acik ret 49, ortuk ret 660 bir yonde izinli oldugu halde reddedilen: 63 istek — hepsi hesap sinirini asiyor
Sekiz yüz kırk isteğin yüz otuz biri izinli. İlk iki satır aynı hesapla hesap sınırının neden ayrı sütun olduğunu gösteriyor: aynı hesapta yalnız kimlik yönünde izinli yirmi dokuz istek var ve yirmi dokuzu izinli sonuçlanıyor, yalnız kaynak yönünde izinli altı istek de öyle. Hesap sınırında aynı satırlar kırk beş ve on sekiz, ama izinli sonuç sıfır.
Son satır bunu tek sayıya indiriyor: altmış üç istek bir yönde açıkça izinli olduğu hâlde reddediliyor. Birileri gerçekten bir belge yazdı, belge eşleşiyor, ama karşı yön yazılmadığı için erişim olmuyor. Hesap sınırının yaptığı iş budur — bir yönün yazılmasını yeterli olmaktan çıkarıp iki tarafın anlaşmasını zorunlu kılar.
Hesap Sınırı
// kosum2.mjs — hesap siniri: kaynak temelli belge ne zaman zorunlu oluyor import { ISTEK, KAYNAK_BELGE, KIMLIK_BELGE, TUR_HESAP, OZNE_HESAP, OZNE, desen, kimlikBelgeleri, yon, degerlendir, izde, IZ } from './yon.mjs'; const disHesap = ISTEK.filter((i) => OZNE_HESAP[i.ozne] !== i.kaynak.hesap); console.log('kaynak turu hesap izin veren belge sinir asan istek izin kimlik izinli ama ret'); for (const t of Object.keys(TUR_HESAP)) { const d = disHesap.filter((i) => i.kaynak.tur === t); const belge = KAYNAK_BELGE.filter((b) => b.etki === 'izin' && b.kaynak.some((p) => desen(p, `${t}/uretim`))).length; const izin = d.filter((i) => degerlendir(i) === 'izin').length; const kesik = d.filter((i) => degerlendir(i) !== 'izin' && yon(kimlikBelgeleri(i), i) === 'izin').length; console.log(`${t.padEnd(18)} ${TUR_HESAP[t].padEnd(15)} ${String(belge).padEnd(17)} ${String(d.length).padEnd(17)} ` + `${String(izin).padEnd(5)} ${kesik}`); } const kesim = (kb, rb) => ISTEK.filter((i) => degerlendir(i, kb, rb) === 'izin'); const mesru = (l) => l.filter(izde).length; const tam = kesim(KIMLIK_BELGE, KAYNAK_BELGE); console.log(`\nizinli istek ${tam.length}; izde gorunen ${mesru(tam)}, kullanilmayan izin ${tam.length - mesru(tam)}`); console.log(`iz ${IZ.size} (ozne, fiil, tur) uclusu tasiyor\n`); // Kaynak temelli belgeyi daraltmanin uc kademesi const r2Dar = KAYNAK_BELGE.map((b) => (b.ad === 'r2' ? { ...b, ozne: OZNE.filter((o) => [...IZ].some((k) => k.startsWith(`${o}|`) && k.endsWith('|nesne-deposu'))) } : b)); console.log('kaynak temelli belge kumesi izin izde gorunen duran mesru is'); for (const [ad, rb] of [['degismeden (r2 ozne *)', KAYNAK_BELGE], ['r2 ozne listesi ize gore', r2Dar], ['r2 kaldirildi', KAYNAK_BELGE.filter((b) => b.ad !== 'r2')], ['hicbiri yok', []]]) { const k = kesim(KIMLIK_BELGE, rb); console.log(`${ad.padEnd(34)} ${String(k.length).padEnd(5)} ${String(mesru(k)).padEnd(13)} ${mesru(tam) - mesru(k)}`); }
kaynak turu hesap izin veren belge sinir asan istek izin kimlik izinli ama ret olcum-deposu hesap-olcum 1 75 12 12 kayit-akisi hesap-olcum 1 75 0 12 kenar-dugum-grubu hesap-olcum 1 75 9 3 nesne-deposu hesap-analitik 1 90 18 0 islev-paketi hesap-analitik 0 90 0 6 makine-kumesi hesap-ortak 1 75 0 6 ag-yapilandirmasi hesap-ortak 1 75 0 6 izinli istek 131; izde gorunen 75, kullanilmayan izin 56 iz 32 (ozne, fiil, tur) uclusu tasiyor kaynak temelli belge kumesi izin izde gorunen duran mesru is degismeden (r2 ozne *) 131 75 0 r2 ozne listesi ize gore 119 75 0 r2 kaldirildi 107 69 6 hicbiri yok 86 60 15
islev-paketi satırı kuralı en yalın biçimde gösteriyor: bu türe bağlı izin veren bir kaynak
belgesi yok, dolayısıyla hesap sınırını aşan doksan isteğin hepsi reddediliyor ve altısı kimlik
tarafında yazılmış izinlere sahip. O altı izin bir yerde duruyor, bakımı yapılıyor, denetimde
görünüyor ve hiçbir işe yaramıyor. kayit-akisi satırında bir izin belgesi var ama saydığı iki
özne aynı hesapta olduğu için sınır aşan yetmiş beş isteğin hiçbirine yaramıyor.
Alt tablo daraltmanın kademelerini veriyor. r2 belgesinin * özne listesi otuz günlük izde
görünen öznelerle değiştirilince izinli istek yüz otuz birden yüz on dokuza iniyor ve duran meşru
iş sıfır: on iki iznin tamamı kullanılmayan izindi. r2 tamamen kaldırılınca izin yüz yediye
düşüyor ve altı meşru iş duruyor; bütün kaynak temelli belgeler kaldırılırsa izin seksen altıya,
duran meşru iş on beşe çıkıyor. Daraltmanın ilk kademesi bedava, ikincisi değil.
Neden Reddedildi
// kosum3.mjs — bir erisimin neden reddedildigini bulmak kac belgeye bakmayi gerektiriyor import { ISTEK, OZNE_HESAP, kimlikBelgeleri, kaynakBelgeleri, yon, degerlendir } from './yon.mjs'; const neden = (i) => { const a = yon(kimlikBelgeleri(i), i), b = yon(kaynakBelgeleri(i), i); if (a === 'acik-ret') return 'kimlik yonunde acik ret'; if (b === 'acik-ret') return 'kaynak yonunde acik ret'; if (a === 'izin' || b === 'izin') return 'hesap siniri: tek yon izin veriyor'; return 'iki yonde de izin yok'; }; const ret = ISTEK.filter((i) => degerlendir(i) !== 'izin'); const belge = (i) => kimlikBelgeleri(i).length + kaynakBelgeleri(i).length + 1; // +1: hesap kurali const ort = (l, f) => (l.reduce((a, x) => a + f(x), 0) / l.length).toFixed(1); console.log(`reddedilen ${ret.length} istek; bakilacak belge = ozneye bagli + kaynaga uyan + hesap kurali\n`); console.log('ret nedeni istek belge ortalama en cok'); for (const n of ['kimlik yonunde acik ret', 'kaynak yonunde acik ret', 'hesap siniri: tek yon izin veriyor', 'iki yonde de izin yok']) { const l = ret.filter((i) => neden(i) === n); console.log(`${n.padEnd(35)} ${String(l.length).padEnd(6)} ${String(ort(l, belge)).padEnd(15)} ` + `${Math.max(...l.map(belge))}`); } console.log(`${'toplam'.padEnd(35)} ${String(ret.length).padEnd(6)} ${String(ort(ret, belge)).padEnd(15)} ` + `${Math.max(...ret.map(belge))}`); console.log(`tek yon okunsaydi (yalniz kimlik): ortalama ${ort(ret, (i) => kimlikBelgeleri(i).length)} belge`); // Tek yone bakan bir okuyucu kac istekte yanlis cevap verir const yalniz = (i) => (yon(kimlikBelgeleri(i), i) === 'izin' ? 'izin' : 'ret'); const gercek = (i) => (degerlendir(i) === 'izin' ? 'izin' : 'ret'); const yanlis = ISTEK.filter((i) => yalniz(i) !== gercek(i)); console.log(`\nyalniz kimlik yonune bakan okuyucu ${ISTEK.length} istegin ${yanlis.length} tanesinde yanilir`); console.log(`bunlarin ${yanlis.filter((i) => OZNE_HESAP[i.ozne] !== i.kaynak.hesap).length} tanesi hesap sinirini asiyor`); const aralik = (f) => { const v = ISTEK.map(f); return `${Math.min(...v)}-${Math.max(...v)}`; }; console.log(`ozneye bagli belge ${aralik((i) => kimlikBelgeleri(i).length)}, ` + `kaynaga uyan belge ${aralik((i) => kaynakBelgeleri(i).length)}`);
reddedilen 709 istek; bakilacak belge = ozneye bagli + kaynaga uyan + hesap kurali ret nedeni istek belge ortalama en cok kimlik yonunde acik ret 28 4.4 5 kaynak yonunde acik ret 21 3.9 4 hesap siniri: tek yon izin veriyor 63 3.5 5 iki yonde de izin yok 597 3.5 5 toplam 709 3.5 5 tek yon okunsaydi (yalniz kimlik): ortalama 1.4 belge yalniz kimlik yonune bakan okuyucu 840 istegin 51 tanesinde yanilir bunlarin 45 tanesi hesap sinirini asiyor ozneye bagli belge 1-2, kaynaga uyan belge 0-2
Bir retin nedenini bulmak ortalama üç buçuk belge okumayı gerektiriyor, en kötü durumda beş. Sayının küçük olmasının nedeni modelin küçük olmasıdır; asıl bilgi oranda. Tek yön okunsaydı ortalama bir virgül dört belgeye bakılacaktı: ikinci yönü eklemek okunacak belgeyi iki buçuk kat artırıyor ve bu artış her ret sorgusunda ödenir.
Bedelin karşılığı son iki satırda. Yalnız kimlik yönüne bakan bir okuyucu sekiz yüz kırk isteğin elli birinde yanlış cevap verir — hepsinde “izinli” der, oysa istek reddedilir. Kırk beşinin nedeni hesap sınırıdır, kalan altısında kaynak yönünde bir açık ret vardır. Belge doğru okunmuştur; yanlış olan tek belgeye bakmaktır.
Özet
- 840 isteğin 131’i izinli; 285 istek aynı hesapta, 555’i hesap sınırını aşıyor.
- 63 istek bir yönde açıkça izinli olduğu hâlde reddediliyor ve hepsi hesap sınırını aşıyor; aynı hesapta bu sayı sıfırdır, çünkü orada bir yön yeter.
islev-paketitüründe izin veren kaynak belgesi yok: sınır aşan 90 isteğin tamamı reddediliyor ve 6 kimlik temelli izin hiçbir işe yaramadan duruyor.- İzinli 131 isteğin 75’i izde görünüyor;
r2’nin*özne listesini ize göre yazmak 12 kullanılmayan izni kapatıyor ve hiçbir meşru işi durdurmuyor. - İkinci kademe bedava değil:
r2kaldırılınca 6, bütün kaynak temelli belgeler kaldırılınca 15 meşru iş duruyor. - Bir retin nedenini bulmak ortalama 3,5 belge ister, tek yön okunsaydı 1,4; yalnız kimlik yönüne bakan okuyucu 840 isteğin 51’inde yanılır.
Sonraki Adım
Bu dersin sekiz öznesi kalıcı kimliklerdi ve her birinin uzun ömürlü bir kimlik bilgisi vardı. İlk derste sayılan yüzey buydu: yirmi dokuz saklama yeri ve sekiz yüz seksen günlük bir anahtar. İzin bağlantısı ne kadar dar yazılırsa yazılsın, anahtar sızdığında bütün belgeler onu taşıyanın lehine çalışır. Sonraki ders erişimi kalıcı kimlikten ayırır: bir özne başka bir kimliği geçici olarak üstlenir. Ölçülen kalemler sızıntı penceresi, üstlenme zincirinin uzunluğu, güven ilişkisinin kimin üstlenebileceğini belirlemesi ve denetim kaydında asıl öznenin kaç adım sonra kaybolduğudur.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.