Compare commits

...

4 commits

Author SHA1 Message Date
oskar 2df4f1dbc3 fix(kb): check_okf.py flags control bytes (NUL etc.) outside \t\n\r
The validator only ever parsed frontmatter fields — it never scanned body
content, so a file with literal NUL bytes pasted in from a psql SELECT
(kb/audits/wiki-kompilat-recon-2026-08-26.md) passed silently. Adds a
whole-file scan for control bytes other than tab/newline/CR, one error per
offending line. Covered by scripts/kb/tests/test_check_okf.py.
2026-08-27 15:53:35 +02:00
oskar e20845ae1b docs(kb): dopisz inwariant 7 (izolacja retrievalu kompilacji od source=wiki) do faza3 §8.1
Decyzja (f) z kb/audits/wiki-kompilat-recon-2026-08-26.md §10, zatwierdzona
przez operatora w całości 2026-08-27. Kompilacja strony wiki nigdy nie czyta
source='wiki' jako dowodu (exclude_sources=('wiki',)); tylko /search
(warstwa użytkownika, po syntezie odpowiedzi) widzi wiki w kaskadzie —
mitygacja self-citation/citogenesis przy źródle retrievalu.

Dopisane jako rozszerzenie po inwariantach 1-6, które pozostają nietknięte
(szkic operatora 2026-07-15 nadal "NIE podlega zmianie").
2026-08-27 15:53:35 +02:00
oskar d8ff94ca1b docs(kb): oznacz decyzje (a)-(h) wiki-kompilat-recon jako zatwierdzone, usuń wklejone bajty NUL
Operator zatwierdził pakiet (a)-(h) z §10 w całości bez zmian 2026-08-27.
Plik zawierał 4 dosłowne bajty NUL (offsety ~37383/37891/41544/47333) —
wklejki z SELECT-ów po korpusie mailowym — które sprawiały, że narzędzia
tekstowe (grep) traktowały plik jako binarny; usunięte, treść w tych
miejscach pozostaje czytelna (puste `` obok `�` — oba demonstrują literalnie
bajt, którego dotyczą).
2026-08-27 15:53:35 +02:00
oskar 17e7cb0aea docs: session 2026-08-27 13:49 2026-08-27 15:49:48 +02:00
5 changed files with 123 additions and 7 deletions

View file

@ -0,0 +1,23 @@
## Session 13:49
### Commits
a97cec0 merge: task/drobne-fixy (resolve-requests 775, prune out of health-monitor, events doc, redeploy action_id)
1dca438 fix(supervisor): apply started_at suffix to redeploy action_id too
40d78ce docs: fix events layout drift in CLAUDE.md
9a86843 fix(monitor): remove unfiltered docker container prune from health-monitor.sh
89f75c3 fix(observer): make world/resolve-requests/ group-writable
### Files changed
CLAUDE.md | 2 +-
scripts/monitor/health-monitor.sh | 110 +++------------------
scripts/observer/observer.py | 12 +++
services/control-plane/src/supervisor.py | 40 ++++----
.../control-plane/tests/test_incident_lifecycle.py | 17 ++++
.../tests/test_supervisor_action_id_uniqueness.py | 50 +++++++++-
6 files changed, 109 insertions(+), 122 deletions(-)
### Deploys
- control-plane → VPS: tagged 4/4 images `:rollback-pre-drobnefixy`, `git pull` (03441a1→a97cec0) + `docker compose up -d --build --force-recreate` (direct, no deploy-local.sh, per 26.08 precedent). Result: 4/4 healthy, zero error/traceback in logs since restart, sha256 of observer.py and supervisor.py match HEAD, `world/resolve-requests/` mode 775 confirmed, incidents.json stable (md5 unchanged) over 3 observer cycles. 30s permission test: flag `test-perms-123` dropped via plain `ssh` as `oskar` (no docker exec) was picked up and deleted in ~5s with `WARNING - Resolve-request flag for unknown incident test-perms-123 — removing flag` — 775 confirmed working in practice.
### Narrative
> _user-provided summary_

View file

@ -3,7 +3,7 @@ okf: "0.1"
type: audit
visibility: private
status: active
updated: 2026-08-26
updated: 2026-08-27
as_of: 2026-08-26
links:
- ../phases/kb-m5-faza3.md
@ -663,7 +663,7 @@ wiki-kompilat:
- **NUL w `entities` jsonb jest poważniejszy strukturalnie** — jeśli sesja
kompilująca odpytuje `entities` bezpośrednio (nie tylko `document_chunk.text`),
NUL bajt w jsonb może wywrócić parser po stronie klienta (Pythonowy
`json`/`asyncpg` różnie reaguje na `` w zależności od ścieżki). To
`json`/`asyncpg` różnie reaguje na `` w zależności od ścieżki). To
**nie zostało zweryfikowane w tym reconie** (nie testowałem odczytu takiego
wiersza) — flaguję jako `[do weryfikacji]`, nie jako potwierdzony problem.
@ -671,7 +671,7 @@ wiki-kompilat:
(charset/NUL) — skala nie uzasadnia opóźnienia PoC. Warto natomiast, żeby
lint (inwariant 3) przy pierwszym uruchomieniu zrobił **jednorazowy skan**
`document_chunk`/`envelope.entities` pod kątem znaków zastępczych
(`<60>`) i `` w źródłach, które faktycznie zasiliły PoC (17 stron
(`<60>`) i `` w źródłach, które faktycznie zasiliły PoC (17 stron
§3) — tanie zabezpieczenie, żeby nie odkryć problemu dopiero przy spot-checku
tygodnie później.
@ -721,7 +721,7 @@ Zależy od Etapu 1 (mechanizm zweryfikowany). Zakres:
semantyczna to zadanie dla CC, nie dla skryptu; skrypt robi tylko
pre-filtr kandydatów).
2. Rozszerzenie o pozostałe encje z §3 (do 17), plus jednorazowy skan pod
kątem `<60>`/`` (§8 „follow-upy fazy mailowej") na źródłach, które
kątem `<60>`/`` (§8 „follow-upy fazy mailowej") na źródłach, które
faktycznie zasiliły te strony.
3. **DoD etapu**: `lint.py` uruchamiany ręcznie (nie timer — zgodnie z §4,
automatyzacja poza zakresem), raport w `_meta/lint-reports/`, zero
@ -743,6 +743,11 @@ Zależy od Etapu 1 (mechanizm zweryfikowany). Zakres:
## 10. Punkt 9 — Decyzje operatora
> **Status: (a)-(h) ZATWIERDZONE przez operatora w całości 2026-08-27, bez
> zmian** — wzorzec identyczny jak decyzje (a)-(g) w `mail-sync-2026-08-06.md`
> (zatwierdzone 2026-08-06 w całości, patrz `kb-m5-faza-mailowa.md` nagłówek
> statusu).
Wzorzec (a)-(g) z `kb/audits/mail-sync-2026-08-06.md` §4.
### (a) Wersja OKF dla `kb-wiki`: pin v0.1 (jak pilot) vs start od razu na v0.2
@ -822,7 +827,7 @@ mini-śledztwa kontekstu firmy.
### (h) Follow-upy fazy mailowej (charset/NUL) — nie blokują, jednorazowy skan przy Etapie 2
**Rekomendacja:** (§8) nie opóźniać PoC na R1R3 z incydentu 20-dni-ciszy;
dopisać jednorazowy skan `<60>`/`` do zakresu Etapu 2 (lint), nie jako
dopisać jednorazowy skan `<60>`/`` do zakresu Etapu 2 (lint), nie jako
osobny, wcześniejszy krok.
---
@ -843,4 +848,5 @@ zweryfikować mechanizm entity-resolution na trudnym przypadku — aliasach —
zanim zainwestuje się w skalę), z jawnym odłożeniem pipeline'u eskalacji i
integracji retrievalu produkcyjnego do kolejnych etapów.
Do decyzji operatora: (a)-(h) w §10.
Decyzje: (a)-(h) w §10 — **ZATWIERDZONE przez operatora w całości 2026-08-27,
bez zmian.**

View file

@ -3,7 +3,7 @@ okf: "0.1"
type: phase
visibility: private
status: active
updated: 2026-07-17
updated: 2026-08-27
links: []
---
@ -628,6 +628,17 @@ Adapter i embed są już idempotentne — nowość to wyłącznie orkiestracja i
> półprodukt kompilacji) i PO filtrze śmieciowych chunków. Pełna wiki po fazie mailowej
> (przyrostówka) — wcześniej kompilat byłby fotografią przeszłości.
**Inwariant 7 (dodany 2026-08-27 — rozszerzenie, nie zmiana inwariantów 16
powyżej, które pozostają NIE podlega zmianie):** decyzja (f),
`kb/audits/wiki-kompilat-recon-2026-08-26.md` §10, zatwierdzona przez operatora
w całości 2026-08-27. Kompilacja strony wiki **nigdy** nie czyta `source='wiki'`
jako dowodu (`exclude_sources=('wiki',)` w `cascade_retrieve`/`hybrid_retrieve`) —
retrieval na potrzeby kompilacji zawsze wyklucza wiki, czyta wyłącznie warstwę
dowodową (mail, paperless). Tylko `/search` (warstwa użytkownika, po zbudowaniu
syntezy odpowiedzi — inwariant 5, faza 5) widzi wiki w kaskadzie. Mitygacja
self-citation/citogenesis przy źródle retrievalu, nie tylko przez lint (inwariant
3) po fakcie — patrz audyt §7 dla pełnego rozumowania.
### 8.2 Rozwinięcie wykonawcze (szczegóły, decyzje architektoniczne bez zmian)
**Repozytorium** (rozstrzygnięcie punktu 6 szkicu): osobne repo `kb-wiki` — decyzja 7,

View file

@ -18,6 +18,8 @@ reguły tego repo:
10. Wpisy `contradicts` wyglądające jak ścieżka .md też muszą istnieć;
pozostałe wpisy to wolny tekst.
11. `stub` o ile obecne musi być boolem.
12. Brak bajtów kontrolnych poza \t \n \r w całym pliku (łapie wklejki NUL/inne
binarne śmieci wklejone z zewnętrznych źródeł, np. wyników SELECT-ów).
Zakres domyślny: kb/ oraz docs/sessions/, z wyłączeniem README-wskaźników
(POINTER_GLOBS) te nawigacją do kb-doca, nie dokumentami KB, i celowo nie
@ -71,6 +73,7 @@ POINTER_GLOBS = (
EXCLUDE_DIRS = ("build",)
DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
CONTROL_CHAR_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]")
def is_pointer(rel: str) -> bool:
@ -232,6 +235,17 @@ def check_file(path: Path, root: Path) -> list[str]:
f"{rel}: `contradicts` wskazuje na nieistniejący plik: {item}"
)
seen_lines: set[int] = set()
for m in CONTROL_CHAR_RE.finditer(text):
line_no = text.count("\n", 0, m.start()) + 1
if line_no in seen_lines:
continue
seen_lines.add(line_no)
errors.append(
f"{rel}: bajt kontrolny {ord(m.group()):#04x} w linii {line_no} "
"(dozwolone tylko \\t \\n \\r)"
)
return errors

View file

@ -0,0 +1,62 @@
"""Tests for check_okf.py — the OKF v0.1 frontmatter validator.
Covers the control-character check added 2026-08-27: a file that otherwise has
valid frontmatter must still fail if its body contains a NUL byte or other
control byte outside \\t \\n \\r (the class of bug found in
kb/audits/wiki-kompilat-recon-2026-08-26.md raw bytes pasted in from a psql
SELECT over the mail corpus).
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import check_okf # noqa: E402
VALID_FRONTMATTER = """---
okf: "0.1"
type: decision
visibility: private
status: active
updated: 2026-08-27
links: []
---
"""
def _write(tmp_path: Path, body: str) -> Path:
p = tmp_path / "doc.md"
p.write_text(VALID_FRONTMATTER + body, encoding="utf-8")
return p
def test_clean_file_has_no_control_char_errors(tmp_path):
path = _write(tmp_path, "# Title\n\nZwykła treść z \t tabulatorem i \r\n końcem linii.\n")
errors = check_okf.check_file(path, tmp_path)
assert errors == []
def test_nul_byte_in_body_is_flagged(tmp_path):
path = _write(tmp_path, "# Title\n\nZawiera bajt NUL: \x00 w tym miejscu.\n")
errors = check_okf.check_file(path, tmp_path)
assert any("bajt kontrolny" in e and "0x0" in e for e in errors)
def test_control_char_error_reports_correct_line(tmp_path):
body = "linia 1\nlinia 2\nzepsuta \x00 linia 3\nlinia 4\n"
path = _write(tmp_path, body)
errors = check_okf.check_file(path, tmp_path)
control_errors = [e for e in errors if "bajt kontrolny" in e]
assert len(control_errors) == 1
# Frontmatter occupies 7 lines before the body starts.
frontmatter_lines = VALID_FRONTMATTER.count("\n")
expected_line = frontmatter_lines + 3
assert f"w linii {expected_line}" in control_errors[0]
def test_multiple_control_chars_same_line_reported_once(tmp_path):
path = _write(tmp_path, "para \x00 z dwoma \x00 bajtami na tej samej linii\n")
errors = check_okf.check_file(path, tmp_path)
control_errors = [e for e in errors if "bajt kontrolny" in e]
assert len(control_errors) == 1