# SESSION_HANDOFF — CyberBrain26 v4.1

**status: superseded** — nahrazeno `SESSION_HANDOFF_2026-11-03.md` (3. 11. 2026)
**Datum:** 2. října 2026
**Větev:** `master` · **HEAD:** `90a42e6` (`chore: complete Audit B spaces and security tests`)
**Co to byla:** implementační session, která uzavřela dvě otevřené úlohy z předchozí session, zapsala rozhodnutí D1–D3 a udělala `cyberbrain run` fail-closed (D2) + model-free (D3). Žádný commit, žádné stakování, žádný reset, žádné mazání.

Detailní kontext: [PROJECT_STATUS.md](PROJECT_STATUS.md), [docs/SECURITY_ONE_PAGER.md](docs/SECURITY_ONE_PAGER.md), [docs/product/DECISIONS_LOG_V5.md](docs/product/DECISIONS_LOG_V5.md), [ROADMAP_V5.md](ROADMAP_V5.md), [EXECUTION_SECURITY_REVIEW.md](EXECUTION_SECURITY_REVIEW.md).

> **Novější handoff (4. 10. 2026):** [.kilo/context/handoff/HANDOFF_2026-10-04-mobile-channel.md](.kilo/context/handoff/HANDOFF_2026-10-04-mobile-channel.md) — mobilní kanál (Telegram + Kilo Android appka): revize plánu R1–R5 schválena, tři runbooky připraveny, Gate 1 **nespuštěn** (orchestrátor nemá předaný `bash`, probe dead-letterován po dvou selháních). Text níže platí pro 2. 10. 2026 a není jím nahrazován.

---

## Stav v jedné větci

Prototyp testuje a lintuje čistě (369 testů, Ruff, mypy), `cyberbrain run` je fail-closed a nevolá model, ale produkt je stále zablokovaný na **zákaznické discovery (0 rozhovorů)** a pracovní strom **není** čistý release baseline — ten záměr nebyl v této session udělán.

---

## Ověřený stav

Ověřeno na tomto hostiteli 2. 10. 2026, pracovní strom včetně všech necommitnutých změn:

```
PYTHONPATH=src python3 -m pytest -q                    →  369 passed
python3 -m ruff check .                                →  All checks passed!
PYTHONPATH=src python3 -m mypy src/cyberbrain26       →  Success: no issues found in 57 source files
bwrap --version                                         →  bubblewrap 0.9.0
python3 --version                                       →  Python 3.12.3
```

S Bubblewrap skutečně skrytým z `PATH` (`shutil.which("bwrap") is None`):

```
PYTHONPATH=src python3 -m pytest -q                    →  360 passed, 9 skipped
```

Devět skipů jsou testy vyžadující reálnou sandbox runtime.

Předchozí session na stejném hostiteli naměřila **344 passed**; přírůstek 25 je práce této session.

**Co to NEověřuje:** hostitované CI (`no-bubblewrap-fail-closed` job běží poprvé až v CI), žádného živého externího model providera, a především **to nic neříká o ochraně proti nepřátelskému kódu**. Bubblewrap je prototypní izolace s kooperativními namespace/`RLIMIT_*` limity; bez samostatné bezpečnostní revize se nesmí prezentovat jako ochrana proti libovolnému nepřátelskému kódu.

---

## Rozhodnutí

Zdroj: [docs/product/DECISIONS_LOG_V5.md](docs/product/DECISIONS_LOG_V5.md). Všechna tři jsou **vlastníkova rozhodnutí, ne ověřené zákaznické fakta**. Nepřelamují se tiše.

| ID | Rozhodnutí | Stav | Zdůvodnění (jedna věta) |
|---|---|---|---|
| **D1** | Podporovaný OS = Ubuntu 24.04 LTS, výhradně | rozhodnuto, **čeká na packaging + CI** | Uzavírá Etapu 1 krok 4 a odblokuje balení/CI; rozhodnuto **před** discovery, takže vyžaduje znovupotvrzení proti interview evidence. |
| **D2** | `cyberbrain run` je fail-closed (`AuthorizedExecutionService` + SQLite `PtahBridge`, žádný `--insecure-legacy`) | schváleno, **implementováno** | Každý krok musí mít ověřenou identitu, scope a schválení; vypršelá/znenulá/nedostupná brána má zavřít akci, ne ji propustit. |
| **D3** | `cyberbrain run`/`plan` model-free; `ModelGateway.chat` odstraněn, `--model`/`--lang` smazány | schváleno, **implementováno** | Modelový turn nemá v policy systému žádnou akci ani scope, takže ho nelze autorizovat; spuštění modelového textu v sandboxu by bylo trusted input → untrusted execution. |

**Viditelný důsledek D3, který je nutné říct nahlas:** roadmapa z `cyberbrain plan` má planner kroky s prázdným `action`, takže `cyberbrain run` na ní **spustí nic a každý krok odmítne**. To je záměr, ne bug. D3 zároveň **neřeší** otázku prvního coding-agent vstupního bodu (`ROADMAP_V5.md` Etapa 2 P0) — ta zůstává otevřená.

---

## Co se změnilo

| Soubor | Změna |
|---|---|
| `src/cyberbrain26/cli.py` | Přepsán. Nová helper vrstva (`_roadmaps_file` :96, `_load_roadmaps` :101, `_save_roadmaps` :130, `_open_database_or_fail` :139, `_build_bridge` :168, `_parse_scopes` :201, `_validate_approval_timeout` :230, `_declared_action` :243, `_load_roadmap_for_run` :321). Řešení data diru sjednoceno na `product.local_store` (:60) — dřív `_DATA_DIR` ignoroval `CYBERBRAIN_DATA_DIR`, takže `roadmaps.json` a SQLite DB mohly skončit v různých adresářích. `run` jde přes `execute_step` (:394), nepodmíněný tisk „complete“ odstraněn, chyby na stderr + `typer.Exit(1)`. Finální surface: `version`; `plan --goal/-g --id --steps/-n --data-dir --db`; `run --id/-i --data-dir --db --approval-timeout --scopes`; `approvals pending\|show\|approve\|deny\|history`; `doctor check\|main`. `--model`/`--lang` **odstraněny** (zlomová změna). `--scopes` (:456) defaultuje na **prázdný řetězec = neuděluje nic**. |
| `src/cyberbrain26/orchestration/ptah_bridge.py` | Nový keyword-only param `identity_scopes` (:149; default `None` = předchozí chování, scope merge přes update, neznámá role → `ValueError` :282). `execute_step` znovu dispatchuje krok, který byl zablokován, ale dokončen (:417). |
| `src/cyberbrain26/execution_plane/secure_gateway.py` | Nová `ExecutionRefused(SecurityError)` (:19). Vyhazuje ji **pouze** trojice bran před `provider.execute` (:114): validace identity (:76), permission DENY (:97), nedostupné/denied/expired/cancelled approval (:160, :222). Hranice, za kterou se už nesmí vyhodit, je popsána v docstringu (:20-29). |
| `src/cyberbrain26/agents/orchestrator.py` | `AgentResult.execution_attempted: bool = True` (:51, fail-safe default). `CoderAgent` ho nastaví na `False` pouze při chycení `ExecutionRefused` (:232). |
| `src/cyberbrain26/control_plane/task_queue/__init__.py` | `retry_failed()` (:335), `retry_blocked()` (:356). |
| `src/cyberbrain26/product/__init__.py` | **Nový soubor** — chyběl, bez něj nešlo naimportovat `product`. |
| `tests/test_product_cli.py` | **Nový** — `approvals` / `doctor` sub-app. |
| `tests/test_cli_run_authorized.py` | **Nový** — měření re-dispatch invariantu. |
| `.github/workflows/ci.yml` | Nový job `no-bubblewrap-fail-closed` (:50): odstraní binárku bwrap, ověří nerozlišitelnost třemi způsoby (`command -v bwrap` :80, `bwrap --version` :84, `shutil.which` :89) a pak tvrdí, že provider odmítne zprávou „refusing to run code without a sandbox“ (:118). Job `quality` beze změny. |
| `docs/SECURITY_ONE_PAGER.md`, `ROADMAP_V5.md`, `PROJECT_STATUS.md`, `docs/product/MVP_GAP_ANALYSIS_V5.md` | Opraveny zastaralé/nepravdivé tvrzení. |

**Opravené nepravdivé tvrzení — nepřenášet dál:** dřívější session tvrdila „12 Ruff chyb v `product/approvals.py`“. **Nepravda.** Soubor byl čistý už dřív a `pyproject.toml` nemá `lint.select`, takže tyhle rule nikdy nebyly zapnuté.

---

## Bezpečnostní invariant

> Krok smí být znovu dispatchován **pouze tehdy**, pokud předchozí pokus skončil odmítnutím **PŘED spuštěním jakéhokoli kódu**.

Naměřená evidence (viz `tests/test_cli_run_authorized.py`):

| Scénář | Výsledek |
|---|---|
| scope odepřený, opakované běhy | 0 exekucí |
| timeout schválení → schváleno ve sdíleném store → druhý běh | druhý běh projde, **přesně 1 exekuce** |
| krok dělající `sys.exit(3)` | 3 běhy → pořád **1 exekuce**, nikdy znovu dispatchován |
| schválení odepřeno při druhém běhu | 0 exekucí |
| scope odebraný před druhým během | 0 exekucí |

Audit chain se při retry prokazatelně zopakuje (`permission_check` a `approval_required` se objeví znovu; `tool_called/started` zůstává 0, dokud není schválení).

Invariant je vynucen **strukturálně**: `ExecutionRefused` + `AgentResult.execution_attempted` + `SQLiteTaskQueue.retry_blocked()`.

**Nikdy neodvozovat „bylo to zablokováno?“ z textu chyby.** Jakákoli taková změna je bezpečnostní regrese — textové shody jsou lokalizovatelné, měnitelné a podvržené.

---

## Nedokončené / otevřené body

1. **Zákaznická discovery nezačala** — 0 rozhovorů, žádný outreach. To je blokující brána pro zbytek MVP scope. `ROADMAP_V5.md` výslovně zakazuje uzavírat OS cíl a první coding-agent integraci na ne-interview evidence (D1 přesto vlastník rozhodl). Kontaktní seznam neexistuje a oslovení kohokoli vyžaduje výslovný pokyn vlastníka.
2. **Git stav nevyřešen.** `HEAD` je `90a42e6`; nic nebylo stakováno, commitnuto, resetováno ani smazáno. Index je **prázdný**, pracovní strom nese 12 změněných sledovaných cest a 6 untracked (`docs/SECURITY_ONE_PAGER.md`, `docs/product/DECISIONS_LOG_V5.md`, `docs/research/`, `src/cyberbrain26/product/`, `tests/test_cli_run_authorized.py`, `tests/test_product_cli.py`). Většina těchto změn existovala už před touto session. **Formování release commitu je rozhodnutí vlastníka a nebylo učiněno.** (Mimo `SESSION_HANDOFF.md`, který je tento dokument.)
3. **Legacy durable job řádky jsou trvale neretryovatelné.** Řádek COMPLETED zapsaný před touto změnou má `success=False` bez značky `execution_attempted`. Fail-closed byl zvolen záměrně; takové řádky je nutné znovu vytvořit (nové id roadmapy/kroku) nebo ručně smazat.
4. **`plan --steps N` nefunguje.** `PlannerAgent.execute` (`src/cyberbrain26/agents/orchestrator.py:153`) ignoruje `max_steps` a vrací vždy `range(1, 4)`. Oprava sahá do `orchestrator.py`.
5. **`plan` zapisuje durable task řádek (`ptah:<id>:step-0`), jehož COMPLETED výsledek se nikdy neobnoví** — opakované `plan` se stejným `--id` tedy znovu použije zastaralý plán.
6. **`--scopes` platí jen pro roli `coder`.** Krok s rolí `security`/`evaluator`, který nese kód, projde scope branou a zablokuje se až později na policy/approval. Zdokumentováno, ale je to past.
7. **Library loggery tiskou celý stack trace na stderr při každém odmítnutém kroku** (`logger.exception` v `ptah_bridge.py:477` a v `task_queue`). Operátor vidí dva tracebacky i na běžném odepření scope. Testy tvrdí na stdout, ne na stderr. Potlačit to z CLI by byla regrese — jde to řešit jen v `ptah_bridge.py`.
8. **Test úspěšného schválení používá thread + `CliRunner`**, což globálně vyměňuje `sys.stdout`/`sys.stderr`; bez `join` v `finally` se rozbije celá sada. Běží ~0.45 s, označen `@requires_bubblewrap`.
9. **ID schválení jsou UUID**, takže dvouterminálový approval flow nelze otestovat čistě sekvenčně; test postupně schvaluje všechny pending požadavky.
10. **Otevřené bezpečnostní mezery, kterých se tato práce nedotkla:** schválení není kryptograficky svázáno s kódem, který se spustí; prompt egress k vzdáleným providerům není ani doložen, ani blokován; `AIFirewall` není v cestě spuštění ani v egressu; chybí cgroup/PID/disk/inode kvóty; `chmod` je best-effort; `prune_expired` není bezpečné mazání; `ACTION_SCOPE_MAP` postrádá čtyři akce.

---

## Co dál

**První věc: spustit zákaznickou discovery.** Konkrétně — vlastník schválí oslovení 8–12 respondentů podle [docs/product/CUSTOMER_DISCOVERY_V5.md](docs/product/CUSTOMER_DISCOVERY_V5.md). Do té doby je každé další rozšiřování MVP scope odhadované, ne odůvodněné.

Inženýrské body **mohou běžet paralelně**, protože na výsledku discovery nezávisí:

- **Packaging + CI pro Ubuntu 24.04 LTS** (D1) — odblokované rozhodnutím, nejde o produktový scope.
- **Rozhodnutí o stavu gitu** — zda a jak se velká změna převede do release commitu. Bez toho je každý další merge zbytečně rizikový.
- **`plan --steps N`** (bod 4) a **nečerstvý durable řádek z `plan`** (bod 5) — malé cílené opravy.
- **Návrat modelové cesty pro prvního coding agenta** — pozor, to je bezpečnostní revize, ne jen UX; `ROADMAP_V5.md` Etapa 2 P0 zůstává otevřená a D3 záměrně nechává otázku nezodpovězenou.
- Nezávislá bezpečnostní revize nasazení — před jakýmkoli použitím nedůvěryhodného kódu.

---

## Jak ověřit

```bash
cd /home/oem/CyberBrain26-v4

git rev-parse HEAD          # očekáváno 90a42e6
git log --oneline -3
git status --short          # očekáváno: prázdný index, necommitnutý strom viz bod 2

PYTHONPATH=src python3 -m pytest -q               # očekáváno: 369 passed
python3 -m ruff check .                           # očekáváno: All checks passed!
PYTHONPATH=src python3 -m mypy src/cyberbrain26   # očekáváno: Success, 57 source files

# fail-closed bez sandboxu — bwrap musí být opravdu nerozlišitelný
bwrap --version                                  # očekáváno: bubblewrap 0.9.0
# Po odstranění binárky bwrap z PATH (např. PATH=/tmp/minbin, kde jsou jen python3/sh/git/...):
python3 -c "import shutil; print(shutil.which('bwrap'))"   # musí vypsat None
PYTHONPATH=src python3 -m pytest -q               # očekáváno: 360 passed, 9 skipped
```
