diff --git a/.deckent/workspace/IDENTITY.md b/.deckent/workspace/IDENTITY.md index 3acb7369a..13db05090 100644 --- a/.deckent/workspace/IDENTITY.md +++ b/.deckent/workspace/IDENTITY.md @@ -17,7 +17,7 @@ Direction (2026-06-29 pivot): Tool-driven, progressive-disclosure, full-control Moat: Deterministik eval-backed orchestration · governance-by-construction · outcome→evidence→routing→promotion→training-trace kapalı öğrenme döngüsü SSOT: `docs/MASTER-PLAN.md` · core-memory: `.deckent/docs/core-memory/MEMORY.md` · yön gerekçesi: `.analysis/hermes-vs-deckent-direction-decisions.md` -Tests: 34,179 descriptors (parsed from tests/**/*.test.ts(x)) +Tests: 34,181 descriptors (parsed from tests/**/*.test.ts(x)) Dashboard Tests: 96 descriptors (parsed from src/dashboard/src/**/*.test.tsx) Coverage: N/A diff --git a/.deckent/workspace/stats-snapshot.json b/.deckent/workspace/stats-snapshot.json index cb131f656..1233863ff 100644 --- a/.deckent/workspace/stats-snapshot.json +++ b/.deckent/workspace/stats-snapshot.json @@ -2,5 +2,5 @@ "$comment": "Deliberately-updated volatile stat snapshot (MASTER-PLAN 521, CI-STATS-HERMETIC-001). Single hermetic source for badge inputs not derivable from tracked files. Refresh: node scripts/update-readme-stats.mjs --refresh-snapshot [--with-coverage] --write", "sprint": 492, "coverage": null, - "refreshedAt": "2026-08-05T21:28:58.653Z" + "refreshedAt": "2026-08-07T11:06:19.141Z" } diff --git a/README.md b/README.md index d6dc327fb..576c97a1c 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Deckent's three immutable laws are Dual Lens + Scale, Every Environment, and Nev License: MIT. [Evidence: `package.json:90-91`; `LICENSE`] -[![npm version](https://img.shields.io/npm/v/deckent.svg)](https://www.npmjs.com/package/deckent) [![tests](https://img.shields.io/badge/tests-34179%2B-brightgreen)](https://github.com/VerhexIO/deckent) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![sprints](https://img.shields.io/badge/sprints-492%2B-teal)](https://github.com/VerhexIO/deckent) [![version](https://img.shields.io/badge/version-v1.0.0--beta.1-orange)](https://github.com/VerhexIO/deckent) [![CI](https://img.shields.io/github/actions/workflow/status/VerhexIO/deckent/ci.yml?label=ci)](https://github.com/VerhexIO/deckent/actions) +[![npm version](https://img.shields.io/npm/v/deckent.svg)](https://www.npmjs.com/package/deckent) [![tests](https://img.shields.io/badge/tests-34181%2B-brightgreen)](https://github.com/VerhexIO/deckent) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![sprints](https://img.shields.io/badge/sprints-492%2B-teal)](https://github.com/VerhexIO/deckent) [![version](https://img.shields.io/badge/version-v1.0.0--beta.1-orange)](https://github.com/VerhexIO/deckent) [![CI](https://img.shields.io/github/actions/workflow/status/VerhexIO/deckent/ci.yml?label=ci)](https://github.com/VerhexIO/deckent/actions) diff --git a/README.tr.md b/README.tr.md index 72dba5699..574d87739 100644 --- a/README.tr.md +++ b/README.tr.md @@ -94,7 +94,7 @@ Deckent'in üç Immutable Law'u Dual Lens + Scale, Every Environment ve Never MV License: MIT. [Kanıt: `package.json:90-91`; `LICENSE`] -[![npm version](https://img.shields.io/npm/v/deckent.svg)](https://www.npmjs.com/package/deckent) [![tests](https://img.shields.io/badge/tests-34179%2B-brightgreen)](https://github.com/VerhexIO/deckent) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![sprints](https://img.shields.io/badge/sprints-492%2B-teal)](https://github.com/VerhexIO/deckent) [![version](https://img.shields.io/badge/version-v1.0.0--beta.1-orange)](https://github.com/VerhexIO/deckent) [![CI](https://img.shields.io/github/actions/workflow/status/VerhexIO/deckent/ci.yml?label=ci)](https://github.com/VerhexIO/deckent/actions) +[![npm version](https://img.shields.io/npm/v/deckent.svg)](https://www.npmjs.com/package/deckent) [![tests](https://img.shields.io/badge/tests-34181%2B-brightgreen)](https://github.com/VerhexIO/deckent) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![sprints](https://img.shields.io/badge/sprints-492%2B-teal)](https://github.com/VerhexIO/deckent) [![version](https://img.shields.io/badge/version-v1.0.0--beta.1-orange)](https://github.com/VerhexIO/deckent) [![CI](https://img.shields.io/github/actions/workflow/status/VerhexIO/deckent/ci.yml?label=ci)](https://github.com/VerhexIO/deckent/actions) diff --git a/docs/MASTER-PLAN.md b/docs/MASTER-PLAN.md index 192cc8f28..6347c5e25 100644 --- a/docs/MASTER-PLAN.md +++ b/docs/MASTER-PLAN.md @@ -369,6 +369,8 @@ Current receipt register: | `GR-2026-08-07-T1-STATS-SUPP-01` | TENANT-001 | G1 | `README.md@cb630f5dd8925537a365433a3d5d87e0266ab8bdb196a3dab92533758e25aabf`; `README.tr.md@fb352c20d9e9bfe593bdbe8810a7f01724fb9018ac5e6620e8c850fb2c239c4b`; `.deckent/workspace/IDENTITY.md@01142d9b664b5087f464476c593e21cd33e5b6d49a8e9bf024266274de78326f`; T1 ek-dilimi: tenant pinleri rozet-sayısını değiştirir; üç hedef mekanik regen | owner=Alperen; decision=APPROVED; scope=exact three-path mechanical stats regen; exclusions=production-code,sprint,provider-call,build,destructive-action,other-files | 2026-08-07T09:36:45Z | `ONE_SHOT`: consumed@2026-08-07T09:36:47Z | | `GR-2026-08-07-TENANT-T2-01` | TENANT-001 | G1 | `src/api/missions-route.ts@f1e3d6035ff7762afc01c196e96fc0e9423111f65e08e6f71724f488d6840733`; `src/api/autonomous-endpoint.ts@05fe6103a27d317d135bad0092ca96bb985bf8d11f67df53f0563a292476868c`; `tests/api/missions-route.test.ts@380855a07502992ee21868e0bac3bfa2695154c0610a8de013c82326f833022f`; `scripts/lint-test-hermeticity.mjs@d1bdbdb9cb975ef9a8a5fef17bc3f9011bb4b31ddcf2114d477273549feb3ff2`; T2 dilimi (T1'in successor kapsamı): T1 yalnız run-flow propose ingress'ini kapattı; aynı NULL-tenant varsayılanı missions (list ve tekil okuma) ve autonomous (chain okuma ve mutation) yollarında da duruyor — dört callsite T1'in resolveCallerTenant çözümüne taşınır ve strict modda typed 403 üretir, strict kapalıyken bugünkü local davranışı bayt-değişmez; IDOR regresyon pinleri her iki modu ve yabancı-tenant görünmezliğini doğrular | owner=Alperen; decision=APPROVED; scope=exact four-path tenant scope propagation to missions and autonomous ingresses; exclusions=sprint,provider-call,build,destructive-action,other-files | 2026-08-07T10:47:29Z | `ONE_SHOT`: consumed@2026-08-07T10:51:43Z | | `GR-2026-08-07-T2-SUPP-01` | TENANT-001 | G1 | `scripts/lint-test-hermeticity.mjs@d1bdbdb9cb975ef9a8a5fef17bc3f9011bb4b31ddcf2114d477273549feb3ff2`; `README.md@d6d2be22d0df937c3599afeb088f162445d78ea7c01a0441dd07067cd142947c`; `README.tr.md@09e7e5a82da9ca7a08f961995a2e9c7344c6fa7ff87d3e1084ed8fcf49885d9b`; `.deckent/workspace/IDENTITY.md@1be0534d4aa827a9266656d7ee5e0173644ba1b86776628e6c646ca61ac9cdd2`; T2 ek-dilimi: yeni tenant-scope modülü ve IDOR pinleri baseline digest'lerini ve rozet-sayısını oynatır; ikisi de mekanik tazelenir | owner=Alperen; decision=APPROVED; scope=exact four-path mechanical baseline and stats refresh; exclusions=production-code,sprint,provider-call,build,destructive-action,other-files | 2026-08-07T10:52:05Z | `ONE_SHOT`: consumed@2026-08-07T10:53:44Z | +| `GR-2026-08-07-TENANT-T3-01` | TENANT-001 | G1 | `src/api/server.ts@92cb4b009c44e202b19aafb6dfb6f57be9559d22070b8db4e41168ab02652d22`; `tests/api/server.test.ts@1b13bfb153bde0b9572f035818e7a37bd13c59c182f70121794c589f7ff98779`; `scripts/lint-test-hermeticity.mjs@45b9bb6c9876a524fd45a992f354154736082d54b54e25a4757c596e592ffdbf`; T3 dilimi (T2 successor'ı): kalan iki merkezi NULL-tenant sitesi server.ts'te — /api/start ingress'i ve /api/terminal/* yüzeyi (terminal, kabuk erişimi taşıdığı için tenant sınırının en duyarlı yüzeyi); ikisi de T2'nin resolveApiCallerTenant kararına taşınır ve strict modda typed 403 üretir, strict kapalıyken bugünkü local davranışı bayt-değişmez; regresyon pinleri her iki modu doğrular | owner=Alperen; decision=APPROVED; scope=exact three-path tenant scope propagation to start and terminal ingresses; exclusions=sprint,provider-call,build,destructive-action,other-files | 2026-08-07T10:56:57Z | `ONE_SHOT`: consumed@2026-08-07T11:00:57Z | +| `GR-2026-08-07-T3-SUPP-01` | TENANT-001 | G1 | `scripts/lint-test-hermeticity.mjs@45b9bb6c9876a524fd45a992f354154736082d54b54e25a4757c596e592ffdbf`; `README.md@1f865e5ea7cf4536ea6e672a251442fb4a64f9629f6f96149148ee176a434db0`; `README.tr.md@c96decf5699c5a9d3cf09dfb130ca6b9e5cf0343f7f42a45611f556e61bab885`; `.deckent/workspace/IDENTITY.md@f30a12ba072bc61010bd1140cb9089f1c034bcce9719306b26a77c616e71bfd5`; T3 ek-dilimi: resolver-kontrat pinleri ve server.ts kablolaması baseline digest'lerini ve rozet-sayısını oynatır; ikisi de mekanik tazelenir | owner=Alperen; decision=APPROVED; scope=exact four-path mechanical baseline and stats refresh; exclusions=production-code,sprint,provider-call,build,destructive-action,other-files | 2026-08-07T11:02:00Z | `ONE_SHOT`: consumed@2026-08-07T11:06:19Z | ### 3.5 Typed blocker register @@ -911,7 +913,7 @@ specification'ını execute eder. Legacy provider adapter'ının varlığı PAEP |---:|---|---|---|---|---|---|---|---|---|---|---|---| | 4000 | AUTHORITY-001 | P04 | AUTHORITY | Unified runtime authority parent | P0 | SSOT-003 | G2,G1 | OPEN | ~/~/~/?/0/?/? | Every operation binds principal, tenant, capability, approval, budget, receipt and audit | Code-truth audit | 2026-07-26 | | 4010 | PRINCIPAL-001 | AUTHORITY-001 | AUTHORITY | VerifiedPrincipal across local, OIDC, workload and connector identities | P0 | SSOT-003 | G2,G1 | DONE | 1/1/1/1/1/-/- | No header-derived or synthetic identity reaches authorization; provenance and assurance explicit | 2026-07-27 code-truth: CLI mission ingress `createdBy` üretmiyor; DO local/synthetic actor ile surface-specific coordinator'a giriyor; Terminal/API identity audit o turda tamamlanmamıştı; `receipt=GR-2026-08-06-PRINCIPAL-P1A-01`; Dalga-2 P1a admission 2026-08-06 (Alperen 'tam kompozisyon' start-gate); 2026-08-06 P1A SETTLEMENT (tren-m): VerifiedPrincipal tipi + resolveLocalOsPrincipal + principalToActor + assessActorAssurance advisory-seam'i; sentetik-literal envanteri kapandı (mcp-operator ×4 / cli-operator / repl-user → gerçek os-user; API üç sitesi tek helper'da, claimsVerified→token-verified ve api-static→doğrulanmamış-güvence AÇIK işaretli); planRunFlow her admission'da typed assurance-kaydı üretir; kanıt-kimliği api-actor-fence-409-then-105files-1017-green; gerçek bulgu: start/cancel route'ları approve'dan farklı çıplak-actor kuruyordu — eşitlik-fence enriched dünyada 409'la yakaladı, birleşik-helper'la kapandı; principal 7/7, api 105/1017, mcp+cli plan suite'leri yeşil, tsc temiz, inventory +1 gerçek modül; enforce-modu P1b dilimi (config-gated) ile; 2026-08-06 EVIDENCE-KONSOLİDASYONU (truth-hizası): P1a settlement'ında truth hücresi güncellenmemişti — ölçülmüş gerçek C=1 (src/core/principal.ts), W=1 (CLI/MCP/API ingress'leri + planRunFlow advisory seam'i wired), H=1 (principal 7/7 + api 105 dosya/1017 test), E=0 (enforce modu P1b'de config-gated gelecek, bugün advisory), L=0 (gerçek-binary kimlik smoke'u P1b kapsamında); `receipt=GR-2026-08-06-PRINCIPAL-P1B-01`; P1b admission 2026-08-06 (Dalga-3 kapısı); 2026-08-06 P1B SETTLEMENT: enforce_principal_assurance config anahtarı (default kapalı) + assertActorAssurance typed enforcement + planRunFlow ingress bağlantısı; kanıt-kimliği principal-enforce-both-modes-11of11; principal 11/11 her iki modu pinler (bayrak-kapalı v1 aynen, bayrak-etkin admission öncesi typed red, gerçek OS kimliği bayrak-açıkken geçer), orchestra+api+mcp 835 dosya/10805 test yeşil, tsc temiz; E boyutu policy-enablement ile kapandı (1); L için gerçek-binary enforce koşusu P1c kapsamındadır; `receipt=GR-2026-08-06-PRINCIPAL-P1C-01`; P1c admission 2026-08-06; 2026-08-06 P1C SETTLEMENT (kısmi — dürüst kayıt): plan komutunda P1a taramasında kaçan İKİ sentetik kimlik sitesi daha gerçek OS kimliğine çevrildi ve adoption yolu doğrulanmamış-güvence olarak dürüstçe işaretlendi; kanıt-kimliği cli-class-528files-7691-green; CLI sınıfı 528 dosya/7691 test yeşil, tsc temiz, lint:gates tam zincir yeşil. L boyutu bu turda KAPANMADI: gerçek-binary enforce probu planlama yolunun daha erken bir durable-publication HOLD'una takıldı ve enforce kararına ulaşamadı — L için plan-flow fixture'lı ayrı gerçek-binary dilimi (P1d) gerekir; PRINCIPAL DONE'u ve Dalga-3 kapısı (OPERATION-001/TENANT-001 admission'ı) P1d'ye bağlıdır; 2026-08-06 P1C SETTLEMENT (kısmi — dürüst kayıt): plan komutunda P1a taramasında kaçan İKİ sentetik kimlik sitesi daha gerçek OS kimliğine çevrildi, adoption yolu doğrulanmamış-güvence işaretlendi; kanıt-kimliği cli-class-528files-7691-green; CLI 528 dosya/7691 test yeşil, tsc temiz, lint:gates tam zincir yeşil. L bu turda KAPANMADI: gerçek-binary enforce probu planlama yolunun daha erken durable-publication HOLD'una takıldı ve enforce kararına ulaşamadı — L için plan-flow fixture'lı gerçek-binary dilimi (P1d) gerekir; PRINCIPAL DONE'u ve Dalga-3 kapısı (OPERATION-001/TENANT-001) P1d'ye bağlıdır; `receipt=GR-2026-08-06-PRINCIPAL-P1D-01`; P1d admission 2026-08-06; 2026-08-06 P1D DURUŞU (kayıpta-dur, dürüst kayıt): restrictive-only kimlik seam'i eklenip gerçek-binary enforce kanıtı denendi ve ÜÇ farklı CLI yolu enforce kararına ULAŞAMADI — dry-run preview yolu planRunFlow'u hiç çağırmıyor, adoption yolu daha erken projection-HOLD veriyor, normal yol ise scope-gate FAIL'inde duruyor (--force-scope dahil). Seam geri alındı (test'siz seam ölü-koddur), receipt revoked. L için doğru dilim: ya scope-gate'i sağlayan gerçek fixture'lı plan-flow harness'ı ya da API ingress'i üzerinden gerçek-sunucu kanıtı — bu, P1d'nin yeniden tasarımını gerektirir ve PRINCIPAL DONE'u ile Dalga-3 kapısı ona bağlıdır; `receipt=GR-2026-08-06-PRINCIPAL-P1D-02`; P1d-v2 admission 2026-08-06 (yeniden tasarım; kök-neden config-carry boşluğu, ölçümle doğrulandı); `receipt=GR-2026-08-07-PRINCIPAL-P1E-01`; P1e admission 2026-08-07 (uçtan-uca red kanıtı — Alperen kararı); 2026-08-07 P1E SETTLEMENT: uçtan-uca red GERÇEK BİNARY + GERÇEK HTTP sunucusuyla kanıtlandı — deckent serve altında kimlik-doğrulamalı ama doğrulanmamış-güvence principal (api-static) ile POST /api/run-flow/propose çağrısı gate AÇIKken HTTP 502 ve typed 'principal assurance typed-red at planRunFlow: actor api-static carries explicit doğrulanmamış-güvence (provenance=api)' ile akış oluşmadan reddedildi; gate KAPALIyken aynı çağrı HTTP 201 ve canlı flow üretti; davranış route suite'ine iki yönlü regresyon-pini olarak eklendi (api sınıfı 105 dosya/1019 test yeşil, tsc temiz); `proof=e2e-real-server-denial-502-vs-201`; 2026-08-07 DONE-ONAYI: Alperen kanıt-paketi üzerinden onayladı — C=1 (src/core/principal.ts), W=1 (CLI/MCP/API ingress'leri + planRunFlow seam'i), E=1 (config-carry ölçümlü etkin), H=1 (principal 14/14 + api 105 dosya/1019), L=1 (gerçek sunucu: gate-ON HTTP 502 typed red, gate-OFF HTTP 201 akış); altı dilim receipt'i (P1a-P1e) consumed; aile kanıt-kimlikleri metin olarak korunarak tek kanonik proof token'ına indirildi; X/S declare-edilmiş matris bulunmadığından not-applicable | 2026-08-07 | -| 4020 | TENANT-001 | AUTHORITY-001 | AUTHORITY | Canonical tenant/project/session scope enforcement | P0 | PRINCIPAL-001 | G1 | VERIFY | 1/1/1/1/0/?/? | Read, write, event, memory, run, flow and admin paths share fail-closed scope; IDOR tests | 2026-07-27 code-truth: Flow raw tenant/id'yi path'e katıyor, iki store layout var, registry/scheduler yalnız flow.id ile key ediyor; Mission/WorkItem IDs global ve API strict tenant composition unwired; `receipt=GR-2026-08-07-TENANT-T1-01`; T1 admission 2026-08-07 (Dalga-3 ikinci yolcu); 2026-08-07 T1 SETTLEMENT: strict_tenant_isolation artık gerçekten kapıyor — core'a resolveCallerTenant + typed TenantScopeError, API propose ingress'inde erken kapı (strict AÇIK: tenant-claim'siz çağıran 403 ile reddedilir ve akış oluşmaz; strict KAPALI: local varsayılanı bayt-değişmez); bulgu P1d ile aynı sınıftı — bayrak yalnız compliance-report'ta okunuyordu, hiçbir kararı etkilemiyordu; ayrıca dilim sırasında bir gerçek hata yakalandı ve düzeltildi: red throw'u try bloğunun dışında kalınca istek askıda kalıyordu (20s timeout), 403 erken yanıta çevrildi; `proof=tenant-strict-mode-3pins-api-106files-1036-green`; kalan kapsam (memory/run/event/admin yolları + IDOR matrisinin tamamı) T2 successor'ıdır; `receipt=GR-2026-08-07-TENANT-T2-01`; T2 admission 2026-08-07; 2026-08-07 T2 SETTLEMENT: tenant-scope kararı missions (liste+tekil) ve autonomous (chain okuma+mutation) ingress'lerine yayıldı — dört callsite tek karara bağlandı; yeni src/api/tenant-scope.ts fail-soft sync bayrak okuyucu + resolveApiCallerTenant (core resolveCallerTenant'ını kullanır; core config-loader-free kalır — P1b kontratı, route'lar senkron olduğundan okuma API katmanında); strict AÇIK tenant-claim'siz çağıranı 403 ile reddeder, strict KAPALI v1 bayt-değişmez, bozuk config sessizce sertleştirmez; `proof=tenant-t2-idor-pins-api-105files-1025-green`; 3 yeni IDOR pini + api sınıfı yeşil; kalan kapsam (memory/run/event/admin CLI+MCP yüzeyleri ve tam IDOR matrisi) T3 successor'ıdır | 2026-08-07 | +| 4020 | TENANT-001 | AUTHORITY-001 | AUTHORITY | Canonical tenant/project/session scope enforcement | P0 | PRINCIPAL-001 | G1 | VERIFY | 1/1/1/1/0/?/? | Read, write, event, memory, run, flow and admin paths share fail-closed scope; IDOR tests | 2026-07-27 code-truth: Flow raw tenant/id'yi path'e katıyor, iki store layout var, registry/scheduler yalnız flow.id ile key ediyor; Mission/WorkItem IDs global ve API strict tenant composition unwired; `receipt=GR-2026-08-07-TENANT-T1-01`; T1 admission 2026-08-07 (Dalga-3 ikinci yolcu); 2026-08-07 T1 SETTLEMENT: strict_tenant_isolation artık gerçekten kapıyor — core'a resolveCallerTenant + typed TenantScopeError, API propose ingress'inde erken kapı (strict AÇIK: tenant-claim'siz çağıran 403 ile reddedilir ve akış oluşmaz; strict KAPALI: local varsayılanı bayt-değişmez); bulgu P1d ile aynı sınıftı — bayrak yalnız compliance-report'ta okunuyordu, hiçbir kararı etkilemiyordu; ayrıca dilim sırasında bir gerçek hata yakalandı ve düzeltildi: red throw'u try bloğunun dışında kalınca istek askıda kalıyordu (20s timeout), 403 erken yanıta çevrildi; `proof=tenant-strict-mode-3pins-api-106files-1036-green`; kalan kapsam (memory/run/event/admin yolları + IDOR matrisinin tamamı) T2 successor'ıdır; `receipt=GR-2026-08-07-TENANT-T2-01`; T2 admission 2026-08-07; 2026-08-07 T2 SETTLEMENT: tenant-scope kararı missions (liste+tekil) ve autonomous (chain okuma+mutation) ingress'lerine yayıldı — dört callsite tek karara bağlandı; yeni src/api/tenant-scope.ts fail-soft sync bayrak okuyucu + resolveApiCallerTenant (core resolveCallerTenant'ını kullanır; core config-loader-free kalır — P1b kontratı, route'lar senkron olduğundan okuma API katmanında); strict AÇIK tenant-claim'siz çağıranı 403 ile reddeder, strict KAPALI v1 bayt-değişmez, bozuk config sessizce sertleştirmez; `proof=tenant-t2-idor-pins-api-105files-1025-green`; 3 yeni IDOR pini + api sınıfı yeşil; kalan kapsam (memory/run/event/admin CLI+MCP yüzeyleri ve tam IDOR matrisi) T3 successor'ıdır; `receipt=GR-2026-08-07-TENANT-T3-01`; T3 admission 2026-08-07; 2026-08-07 T3 SETTLEMENT: kalan iki merkezi NULL-tenant sitesi kapatıldı — /api/plan ingress'i ve /api/terminal/* (kabuk erişimi taşıdığından tenant sınırının en duyarlı yüzeyi); ikisi de T2'nin paylaşılan resolveApiCallerTenant kararına bağlandı (strict AÇIK 403, strict KAPALI v1 bayt-değişmez); doğrulama notu: patch'lenen ilk site 410 ile emekli /api/start DEĞİL canlı /api/plan uçudur — ölü koda kablo çekilmediği kontrol edildi; `proof=tenant-t3-resolver-contract-pins-api-green`; 2 yeni resolver-kontrat pini (strict/permissive/tenant'lı + bozuk-config fail-soft), api sınıfı yeşil, tsc temiz; KAPSAM DÜZELTMESİ (T4 ön-kontrolünde bulundu, aynı gün): T3'ün kapattığı `/api/terminal/*` **HTTP rotalarıdır**; asıl kabuk borusu olan WebSocket upgrade (`attachTerminalGateway`, `src/api/terminal/ws-gateway.ts`) HTTP handler'ından GEÇMEZ ve token-only auth'ta `authTenant='local'` ile kabul edilir — yani HTTP'de 403 alan çağıran WS üzerinden kabuk alabilir; ayrıca T2/T3'ün dayandığı senkron `readStrictTenantIsolation` yalnız proje config'ini okur, `loadConfig`'in global katmanını (`resolveGlobalConfigReadPath`) görmez — global'de strict açan operatörde API katmanı sessizce permissive kalır (bu, daha önce iki kez kapatılan 'raporlar-ama-kapatmaz kontrol' sınıfının üçüncü örneği); ikisi de T4a diliminde kapatılacak, T4b kalan ingress (memory-search, process read+write, enterprise), T4c CLI+MCP 0-hardcode host-tenant çözümü | 2026-08-07 | | 4030 | OPERATION-001 | AUTHORITY-001 | AUTHORITY | Versioned canonical operation catalog | P0 | PRINCIPAL-001 | G2,G1 | VERIFY | 1/1/1/1/0/?/? | Every mutation/read/tool action maps to stable operation ID, risk and effect class | Code-truth audit found no canonical catalog; 2026-08-06 TASARIM-ARTIFACT'I (Dalga-1 paralel, Alperen karar-turu): docs/analysis/operation-catalog-authority-design-2026-08-06.md — şema v1 + zorunlu tüketim zinciri + O1-O5 dilimleme + D1-D3 owner karar noktaları; `receipt=GR-2026-08-06-DESIGN-DOCS-01`; 2026-08-06 MAKİNE-BULGUSU-4: O1 admission'ı dependency-vetolu — DependsOn PRINCIPAL-001 DONE değil (P1a advisory teslim edildi, DONE için E=1 enforce + L=1 gerçek-binary şart). Dalga-3'ün gerçek kapısı P1b'dir; D1-D3 kararları (Tam-8 şema, sayaç-ratchet, JSON+üretilen-tip) kayıtlı ve P1b kapanınca O1 aynen kesilir; `receipt=GR-2026-08-07-OPERATION-O1-01`; O1 admission 2026-08-07 (Dalga-3 başlangıcı — PRINCIPAL DONE ile dependency-veto düştü); 2026-08-07 O1 SETTLEMENT: kanonik katalog kuruldu — sekiz alanlı şema izlenen catalog.v1.json'da (ilk aile fs+memory, 6 operasyon), typed okuyucu + Op.* üretilen sabitleri (0-hardcode), yeni lint-operation-catalog fail-closed doğrular (benzersiz dot-hiyerarşik id, enum üyelikleri, effect→gate MİNİMUM matrisi, capabilities'in work-model sözlüğünde varlığı, i18n tamlığı, sabit-katalog birebir örtüşmesi) ve lint:gates zincirine eklendi; bilinmeyen op id'si typed UnknownOperationError ile fail-closed; `proof=operation-catalog-suite-9of9-and-lint-green`; katalog suite 9/9, lint yeşil, tsc temiz; ingress-kapsam zorlaması (sayaç-ratchet) O3, capability/tool-scope tüketimi O4 dilimidir | 2026-08-07 | | 4040 | CAPABILITY-001 | AUTHORITY-001 | AUTHORITY | Capability authority and progressive disclosure contract | P0 | OPERATION-001, PRINCIPAL-001 | G2,G1 | OPEN | ~/0/0/?/0/?/? | Principal, tenant, operation, resource and environment resolve one scoped capability decision | Existing catalogs are fragmented | 2026-07-26 | | 4050 | APPROVAL-001 | AUTHORITY-001 | AUTHORITY | Runtime-wide durable ApprovalBroker | P0 | PRINCIPAL-001, TENANT-001, OPERATION-001, CAPABILITY-001 | G2,G1 | OPEN | 1/~/~/?/0/?/? | CLI, terminal, Desktop, API, connectors, Worker and Nervous share CAS, expiry, relay and audit | 2026-07-27 code-truth: v2 request actor eksikliğinde park; v1 adapter unknown ID decision yazabiliyor ve API/MCP ingress bunu validate etmiyor; DO gate/revision semantics yüzeyler arasında drift | 2026-07-27 | diff --git a/docs/generated/master-plan-active.json b/docs/generated/master-plan-active.json index e164e80c4..acc2375de 100644 --- a/docs/generated/master-plan-active.json +++ b/docs/generated/master-plan-active.json @@ -3,13 +3,13 @@ "generatedFrom": "docs/MASTER-PLAN.md", "sourceDigest": { "algorithm": "sha256(normalized-lf-utf8)", - "value": "e2af682f03ba8b2fc1828b3a6c46f7e02f1aef49165011e4f884e1bf7e659815" + "value": "9c377a6fcf1aae2b1644cad34cd2faf0c46497ff66ac798aeceead5611fda30c" }, "summary": { "total": 388, "active": 341, "terminal": 47, - "receipts": 109, + "receipts": 111, "byState": { "OPEN": 255, "READY": 0, @@ -3152,7 +3152,7 @@ "state": "VERIFY", "updated": "2026-08-07", "definitionDigest": "7ade16473831b4aeaf6ba4c3c2e23e0278504413a8cbccad5832065e28c1673d", - "progressDigest": "3f4007ecc2f702ee3348cee1aa6214897514b398915484dca5e98dd29b3483c2", + "progressDigest": "a3cd3c98717b90655cf1369135555c635bbc876f016be888d9d8af6c167769f8", "terminalClosureDigest": null }, { @@ -5423,6 +5423,26 @@ "transitionAt": "2026-08-07T10:53:44Z" }, "g7AttemptIdentity": null + }, + { + "id": "GR-2026-08-07-TENANT-T3-01", + "authorityDigest": "688bc1c6e5bff569cf79c56fb5889201caed95548c1c96de11809807ee61d77d", + "lifecycle": { + "mode": "ONE_SHOT", + "status": "consumed", + "transitionAt": "2026-08-07T11:00:57Z" + }, + "g7AttemptIdentity": null + }, + { + "id": "GR-2026-08-07-T3-SUPP-01", + "authorityDigest": "20e8e927c325db4be7b6f4a5d4bfcfde0f6b38e9da48af6ea4dd509dd37b36d4", + "lifecycle": { + "mode": "ONE_SHOT", + "status": "consumed", + "transitionAt": "2026-08-07T11:06:19Z" + }, + "g7AttemptIdentity": null } ], "workItems": [ @@ -15520,10 +15540,11 @@ "closureBlockedBy": [], "evidenceReceipts": [ "GR-2026-08-07-TENANT-T1-01", - "GR-2026-08-07-TENANT-T2-01" + "GR-2026-08-07-TENANT-T2-01", + "GR-2026-08-07-TENANT-T3-01" ], "acceptance": "Read, write, event, memory, run, flow and admin paths share fail-closed scope; IDOR tests", - "evidence": "2026-07-27 code-truth: Flow raw tenant/id'yi path'e katıyor, iki store layout var, registry/scheduler yalnız flow.id ile key ediyor; Mission/WorkItem IDs global ve API strict tenant composition unwired; `receipt=GR-2026-08-07-TENANT-T1-01`; T1 admission 2026-08-07 (Dalga-3 ikinci yolcu); 2026-08-07 T1 SETTLEMENT: strict_tenant_isolation artık gerçekten kapıyor — core'a resolveCallerTenant + typed TenantScopeError, API propose ingress'inde erken kapı (strict AÇIK: tenant-claim'siz çağıran 403 ile reddedilir ve akış oluşmaz; strict KAPALI: local varsayılanı bayt-değişmez); bulgu P1d ile aynı sınıftı — bayrak yalnız compliance-report'ta okunuyordu, hiçbir kararı etkilemiyordu; ayrıca dilim sırasında bir gerçek hata yakalandı ve düzeltildi: red throw'u try bloğunun dışında kalınca istek askıda kalıyordu (20s timeout), 403 erken yanıta çevrildi; `proof=tenant-strict-mode-3pins-api-106files-1036-green`; kalan kapsam (memory/run/event/admin yolları + IDOR matrisinin tamamı) T2 successor'ıdır; `receipt=GR-2026-08-07-TENANT-T2-01`; T2 admission 2026-08-07; 2026-08-07 T2 SETTLEMENT: tenant-scope kararı missions (liste+tekil) ve autonomous (chain okuma+mutation) ingress'lerine yayıldı — dört callsite tek karara bağlandı; yeni src/api/tenant-scope.ts fail-soft sync bayrak okuyucu + resolveApiCallerTenant (core resolveCallerTenant'ını kullanır; core config-loader-free kalır — P1b kontratı, route'lar senkron olduğundan okuma API katmanında); strict AÇIK tenant-claim'siz çağıranı 403 ile reddeder, strict KAPALI v1 bayt-değişmez, bozuk config sessizce sertleştirmez; `proof=tenant-t2-idor-pins-api-105files-1025-green`; 3 yeni IDOR pini + api sınıfı yeşil; kalan kapsam (memory/run/event/admin CLI+MCP yüzeyleri ve tam IDOR matrisi) T3 successor'ıdır", + "evidence": "2026-07-27 code-truth: Flow raw tenant/id'yi path'e katıyor, iki store layout var, registry/scheduler yalnız flow.id ile key ediyor; Mission/WorkItem IDs global ve API strict tenant composition unwired; `receipt=GR-2026-08-07-TENANT-T1-01`; T1 admission 2026-08-07 (Dalga-3 ikinci yolcu); 2026-08-07 T1 SETTLEMENT: strict_tenant_isolation artık gerçekten kapıyor — core'a resolveCallerTenant + typed TenantScopeError, API propose ingress'inde erken kapı (strict AÇIK: tenant-claim'siz çağıran 403 ile reddedilir ve akış oluşmaz; strict KAPALI: local varsayılanı bayt-değişmez); bulgu P1d ile aynı sınıftı — bayrak yalnız compliance-report'ta okunuyordu, hiçbir kararı etkilemiyordu; ayrıca dilim sırasında bir gerçek hata yakalandı ve düzeltildi: red throw'u try bloğunun dışında kalınca istek askıda kalıyordu (20s timeout), 403 erken yanıta çevrildi; `proof=tenant-strict-mode-3pins-api-106files-1036-green`; kalan kapsam (memory/run/event/admin yolları + IDOR matrisinin tamamı) T2 successor'ıdır; `receipt=GR-2026-08-07-TENANT-T2-01`; T2 admission 2026-08-07; 2026-08-07 T2 SETTLEMENT: tenant-scope kararı missions (liste+tekil) ve autonomous (chain okuma+mutation) ingress'lerine yayıldı — dört callsite tek karara bağlandı; yeni src/api/tenant-scope.ts fail-soft sync bayrak okuyucu + resolveApiCallerTenant (core resolveCallerTenant'ını kullanır; core config-loader-free kalır — P1b kontratı, route'lar senkron olduğundan okuma API katmanında); strict AÇIK tenant-claim'siz çağıranı 403 ile reddeder, strict KAPALI v1 bayt-değişmez, bozuk config sessizce sertleştirmez; `proof=tenant-t2-idor-pins-api-105files-1025-green`; 3 yeni IDOR pini + api sınıfı yeşil; kalan kapsam (memory/run/event/admin CLI+MCP yüzeyleri ve tam IDOR matrisi) T3 successor'ıdır; `receipt=GR-2026-08-07-TENANT-T3-01`; T3 admission 2026-08-07; 2026-08-07 T3 SETTLEMENT: kalan iki merkezi NULL-tenant sitesi kapatıldı — /api/plan ingress'i ve /api/terminal/* (kabuk erişimi taşıdığından tenant sınırının en duyarlı yüzeyi); ikisi de T2'nin paylaşılan resolveApiCallerTenant kararına bağlandı (strict AÇIK 403, strict KAPALI v1 bayt-değişmez); doğrulama notu: patch'lenen ilk site 410 ile emekli /api/start DEĞİL canlı /api/plan uçudur — ölü koda kablo çekilmediği kontrol edildi; `proof=tenant-t3-resolver-contract-pins-api-green`; 2 yeni resolver-kontrat pini (strict/permissive/tenant'lı + bozuk-config fail-soft), api sınıfı yeşil, tsc temiz; KAPSAM DÜZELTMESİ (T4 ön-kontrolünde bulundu, aynı gün): T3'ün kapattığı `/api/terminal/*` **HTTP rotalarıdır**; asıl kabuk borusu olan WebSocket upgrade (`attachTerminalGateway`, `src/api/terminal/ws-gateway.ts`) HTTP handler'ından GEÇMEZ ve token-only auth'ta `authTenant='local'` ile kabul edilir — yani HTTP'de 403 alan çağıran WS üzerinden kabuk alabilir; ayrıca T2/T3'ün dayandığı senkron `readStrictTenantIsolation` yalnız proje config'ini okur, `loadConfig`'in global katmanını (`resolveGlobalConfigReadPath`) görmez — global'de strict açan operatörde API katmanı sessizce permissive kalır (bu, daha önce iki kez kapatılan 'raporlar-ama-kapatmaz kontrol' sınıfının üçüncü örneği); ikisi de T4a diliminde kapatılacak, T4b kalan ingress (memory-search, process read+write, enterprise), T4c CLI+MCP 0-hardcode host-tenant çözümü", "updated": "2026-08-07" }, { @@ -19742,6 +19763,6 @@ ], "registryIntegrity": { "algorithm": "sha256(canonical-json-utf8)", - "value": "4137a09da3ffadb5da5fc44da46647e8bae0a55a6d16dca7ebf79d56e106bd66" + "value": "7322740089f36a1befcbdf41f6f31d381e8398d869d607400e2c091b458d3da2" } } diff --git a/docs/generated/master-plan-active.md b/docs/generated/master-plan-active.md index a09d4dbcb..b738abe41 100644 --- a/docs/generated/master-plan-active.md +++ b/docs/generated/master-plan-active.md @@ -5,7 +5,7 @@ **Schema:** 3 -**Source digest:** `sha256(normalized-lf-utf8):e2af682f03ba8b2fc1828b3a6c46f7e02f1aef49165011e4f884e1bf7e659815` +**Source digest:** `sha256(normalized-lf-utf8):9c377a6fcf1aae2b1644cad34cd2faf0c46497ff66ac798aeceead5611fda30c` **Rows:** 388 total · 341 active · 47 terminal diff --git a/scripts/lint-test-hermeticity.mjs b/scripts/lint-test-hermeticity.mjs index 6f8b5657b..24d85060f 100644 --- a/scripts/lint-test-hermeticity.mjs +++ b/scripts/lint-test-hermeticity.mjs @@ -122,7 +122,7 @@ export const UNRESOLVED_BASELINE = Object.freeze({ // 2026-08-06 (P1d): +1 config-carry fixture test. Prior: 485a (12480). // 2026-08-07 (P1e): +2 end-to-end denial pins. Prior: P1d (12481). count: 12483, - digest: '037c7201b738b4230419f38ea0e6e38619094897b58936d6cf52bb09135bbb84', + digest: 'b4417ea9b741663547c76c401e5f09b0f437bc0bab581bb8a28d62e49c4ebb57', }); export const PRODUCTION_INVENTORY_BASELINE = Object.freeze({ @@ -140,7 +140,7 @@ export const PRODUCTION_INVENTORY_BASELINE = Object.freeze({ // 2026-08-06 (P1c): CLI plan identity conversion — same 1198, digest only. // 2026-08-06 (P1d): config carry line + type decls — same 1198, digest only. count: 1201, - digest: '6455c36ef84990dd8c7309470a9147d95c0485c0c8be7c0daf76ce8d6488cb4b', + digest: '24360439f5fb6a7161b52fe3c2629fee028e365389ccc611cd67f56ae93f4951', }); const PROTECTED_ROOT_POLICY = new Map([ diff --git a/src/api/server.ts b/src/api/server.ts index 93dc721af..1efe4831c 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -1,4 +1,5 @@ import { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http'; +import { resolveApiCallerTenant } from './tenant-scope.js'; import { readFileSync, existsSync, readdirSync, writeFileSync, mkdirSync, renameSync, unlinkSync, chmodSync } from 'node:fs'; import { basename, join, extname, resolve } from 'node:path'; import { platform as osPlatform } from 'node:os'; @@ -1432,7 +1433,14 @@ async function handleRequest( return; } const principal = deriveRequestPrincipal(req); - const tenantId = principal.tenantId ?? 'local'; + // TENANT-001 T3: strict mode refuses a tenant-less caller instead of + // folding it into `local` (the NULL-tenant hole). Default-off keeps v1. + const startTenantScope = resolveApiCallerTenant(principal, projectRoot); + if (startTenantScope.tenant === null) { + sendJson(res, { error: startTenantScope.reason }, 403); + return; + } + const tenantId = startTenantScope.tenant; const actor = { id: principal.id, ...(principal.role ? { role: principal.role } : {}), @@ -2612,7 +2620,14 @@ export function createHttpServer( const tok = authHeader.replace(/^Bearer\s+/i, ''); // Derive principal from request bearer (claims read from JWT; unverified before auth gate). const terminalPrincipal = deriveRequestPrincipal(req); - const terminalTenantId: string = terminalPrincipal.tenantId ?? 'local'; + // TENANT-001 T3: the terminal surface carries shell access, so a + // tenant-less caller must not inherit `local` here either. + const terminalTenantScope = resolveApiCallerTenant(terminalPrincipal, projectRoot); + if (terminalTenantScope.tenant === null) { + sendJson(res, { error: terminalTenantScope.reason }, 403); + return; + } + const terminalTenantId: string = terminalTenantScope.tenant; // Async seam (Sprint 268): prefer verifyAsync when the provider defines // it (JWKS key resolution) — the handler is already async. Sync-only // providers (LocalToken) keep the exact previous code path. diff --git a/tests/api/run-flow-routes.test.ts b/tests/api/run-flow-routes.test.ts index 453956d5a..7dd0f07f6 100644 --- a/tests/api/run-flow-routes.test.ts +++ b/tests/api/run-flow-routes.test.ts @@ -486,3 +486,51 @@ describe('TENANT-001 T1 — strict_tenant_isolation', () => { expect(res.body.proposal?.tenant).toBe('local'); }); }); + +// ═══ TENANT-001 T3 — tenant scope reaches the plan + terminal ingresses ═════ +// The API's own `/api/plan` and `/api/terminal/*` surfaces still folded a +// tenant-less caller into `local`. The terminal one carries shell access, so it +// is the most sensitive tenant boundary in the product. These pins assert the +// SHARED resolver behaves identically wherever it is wired. +describe('TENANT-001 T3 — shared tenant resolver contract', () => { + it('strict ON refuses a tenant-less caller, strict OFF keeps local (resolver level)', async () => { + const { resolveApiCallerTenant, readStrictTenantIsolation } = + await import('../../src/api/tenant-scope.js'); + const { mkdtempSync: mk, mkdirSync: md, writeFileSync: wf, rmSync: rm } = await import('node:fs'); + const { tmpdir: td } = await import('node:os'); + const { join: jn } = await import('node:path'); + + const strictRoot = mk(jn(td(), 'tenant-strict-')); + const permissiveRoot = mk(jn(td(), 'tenant-permissive-')); + try { + md(jn(strictRoot, '.deckent'), { recursive: true }); + wf(jn(strictRoot, '.deckent', 'config.json'), JSON.stringify({ strict_tenant_isolation: true })); + + expect(readStrictTenantIsolation(strictRoot)).toBe(true); + expect(readStrictTenantIsolation(permissiveRoot)).toBe(false); // absent config → v1 default + + const tenantless = { id: 'api-static' }; + expect(resolveApiCallerTenant(tenantless, strictRoot).tenant).toBeNull(); + expect(resolveApiCallerTenant(tenantless, permissiveRoot).tenant).toBe('local'); + expect(resolveApiCallerTenant({ id: 'alice', tenantId: 'acme' }, strictRoot).tenant).toBe('acme'); + } finally { + rm(strictRoot, { recursive: true, force: true }); + rm(permissiveRoot, { recursive: true, force: true }); + } + }); + + it('a malformed config fails SOFT to the permissive default (never a silent hard-deny)', async () => { + const { readStrictTenantIsolation } = await import('../../src/api/tenant-scope.js'); + const { mkdtempSync: mk, mkdirSync: md, writeFileSync: wf, rmSync: rm } = await import('node:fs'); + const { tmpdir: td } = await import('node:os'); + const { join: jn } = await import('node:path'); + const root = mk(jn(td(), 'tenant-broken-')); + try { + md(jn(root, '.deckent'), { recursive: true }); + wf(jn(root, '.deckent', 'config.json'), '{ not json'); + expect(readStrictTenantIsolation(root)).toBe(false); + } finally { + rm(root, { recursive: true, force: true }); + } + }); +});