Repository navigation
feat(garden): Duplikate zusammenführen, Widersprüche als Thread, Regressionsschutz, Herkunftstest (REQ-457) - #149
Merged
Conversation
…essionsschutz, Herkunftstest (REQ-457) Teil A von REQ-457 (Job 3 Konsolidieren, Job 5 Regressionsschutz, Kriterium 3 Herkunft). Der Gärtner schlägt weiter nur vor; alles, was er schreibt, ist gestagt und trägt Harness gardener. - Duplikat-Notizen haben auf /ui/review "Zusammenführen": der neueste Eintrag löst die älteren ab (SupersedeKnowledge, Recht wie context_supersede, Grund "Gärtner-Vorschlag #<notiz>, bestätigt von <person>"), die Karte zeigt vorher, was bleibt und was ersetzt wird. - Ein validierter Widerspruch wird ein Thread (verknüpft mit allen Einträgen, Titel als Frage, erster Beitrag mit Begründung und Zitaten) statt einer Notiz. Idempotent über die Stand-Zeile in der Frage: kein zweiter offener Thread, ein geschlossener bleibt zu, solange die Einträge gleich sind. Fällt der Thread aus, entsteht die gestagte Notiz. - Offene Pitfalls bekommen im selben Paket die Regressionsfrage (vorhandener Test, Skizze oder nicht testbar; höchstens 8 je Session, vom Checkout geprüft). Review-Aktionen "Als abgesichert vermerken" und "Als nicht testbar vermerken" rufen SetRegressionCover; Skizzen stehen im Bericht. - Herkunftstest gegen den echten Server, der jeden Schreibzugriff des Gärtners aufzeichnet; Tests der Review-Aktionen mit Lead, Reviewer, Mitglied, Gast und Fremdem.
NurRobin
force-pushed
the
feat/garden-consolidate-regression
branch
from
October 19, 2026 22:52
85a7e0b to
095f5fd
Compare
…n Autor, Titel und Notiz (REQ-457) Befund (Confused Deputy): Wer per POST /api/knowledge mit harness gardener und passender session_ref schrieb, legte eine Notiz an, die wie ein Gärtner-Vorschlag aussah; "Zusammenführen" und "Als abgesichert vermerken" hätten dann auf ein Ziel gewirkt, das die Karte nicht zeigt. - Der Server benennt das Gärtner-Konto (GHOSTTREE_GARDENER_ACCOUNT bzw. --gardener-account, im Store neben dem Zugriffsmodus, nachgeschlagen bei jedem Schreiben). Harness gardener und gardener:-Verweise (jede Schreibweise) darf nur dieses Konto schreiben, auch über supersedes und /api/migrated-knowledge; alle anderen und alle bei fehlender Einstellung bekommen 403 gardener_reserved. - Die Review-Aktionen handeln nur bei einer Notiz, die dieses Konto geschrieben hat (Konto-Nummer aus knowledge_authors, nicht das Label), die seitdem niemand bearbeitet hat und deren Titel dieselben Einträge nennt wie die session_ref; sonst Hinweis auf der Karte, 403 und keine Änderung. - Die Karte zeigt die Einträge der session_ref mit ID, Titel und Link. - Die Testzeile einer Regressions-Notiz hat eine feste Form (relativer Pfad, Doppelpunkt, Funktionsname), wird ganz angezeigt und so eingetragen, wie die Notiz sie hält; die Anfrage trägt keinen eigenen Test. Eine Wiederholung der Prüfung gegen den Checkout geht am Server nicht, er hat keinen. - Negativtests für jede Prüfung, mit Mutationsprobe; das Markdown der deploy/README.md besteht markdownlint wieder.
… ctx account set-gardener (REQ-457) Zweiter Befund der Review: Die Gärtner-Identität wurde über den Kontonamen aufgelöst. Wer seinen Anzeigenamen selbst ändern kann (Profil, Name des Identitätsanbieters), hätte mit dem Namen die Rolle bekommen; ein umbenanntes oder fehlendes Konto hätte sie still verschoben. - Die Einstellung ist die Konto-Nummer, gespeichert in org_state (keine Schemaänderung), gesetzt nur mit `ctx account set-gardener <name|person:N|N> --db <pfad>` auf dem Server-Host (wie die übrigen ctx-account-Befehle: Datenbankzugriff statt Token). Der Befehl löst einmal auf und speichert die Nummer; --clear nimmt die Rolle weg; ctx account list markiert das Konto mit gardener=true; ctx serve loggt gardener_account beim Start. Der laufende Server liest die Einstellung bei jeder Prüfung, ein Neustart ist nicht nötig. - Die Umgebungsvariable und das Flag mit dem Namen entfallen. Es gibt keinen Weg über Namen mehr, also braucht der Name auch keine Reservierung. Nichts, was über HTTP erreichbar ist, schreibt die Einstellung (Test liest die Pakete server, web, mcpserver und client darauf). - Ohne gesetztes Konto darf niemand harness gardener schreiben (403 gardener_reserved, die Meldung nennt den Befehl), und die Review-Aktionen für Gärtner-Notizen sind aus. - Negativtests: Umbenennen des Gärtner-Kontos ändert seine Rechte nicht, ein Konto mit dem alten Namen bekommt keine (über SetOwnName, das der Store für alte Namen schon abweist, und über direkte Namensänderung), Konto wechseln verschiebt die Rolle mit der Nummer; Mutationsprobe mit einer namensbasierten Identität schlägt fehl. - Die Startzeile gardener_account steht in cmdServe neben writer_config und nicht im Handler-Aufbau, den TestServerConcurrentTelemetrySmoke zeilenweise zählt.
…eseweg über den Reader (REQ-457)
…e-regression # Conflicts: # CHANGELOG.md
Contributor
Author
|
Lokale Ergebnisse (Branch
|
6 of 9 tasks
NurRobin
added a commit
that referenced
this pull request
Oct 20, 2026
…te überall, Hinweis auf ctx db vacuum (REQ-526) (#151) # Pull request ## What changed? **Problem (REQ-526, Folgeauftrag zu #150).** Ein bekanntes, geleaktes Passwort steht in den gespeicherten Sitzungen an vielen Stellen ohne Beschriftung: in Befehlszeilen wie `sed 's/<wert>/…/'`, in grep-Ausgaben und als einzelne Wortfragmente (der Volltextindex führt die Teile eines zusammengesetzten Werts als eigene Wörter). Die Schwärzung nach Beschriftung aus #150 erfasst das nicht. Außerdem nannte der Schlusshinweis von `ctx redact-sessions` noch `sqlite3 <db> VACUUM`, und `skills/ghosttree-setup/references/local-server.md` empfahl `sqlite3 … "VACUUM INTO …"` als Sicherung; beides scheitert an `ghosttree_valid_utf8` (Pitfall #3146). **Aufruf.** ```bash ctx redact-sessions --db <pfad> [--dry-run] [--batch N] [--sleep D] [--after-id N] [--skip-optimize] --literals-file <pfad> ``` Die Datei enthält einen bekannten Wert je Zeile. Leerzeilen und Zeilen mit `#` am Anfang werden ignoriert (ein Wert, der selbst mit `#` beginnt, wird `\#` geschrieben), Leerraum um den Wert wird abgeschnitten. Jedes Vorkommen jedes Werts wird durch `[REDACTED:literal]` ersetzt, mit oder ohne Beschriftung. Ohne den Schalter bleibt alles, wie es ist. **Was ersetzt wird** (jeweils in dem Lauf, den der Befehl ohnehin macht): | Ort | Verhalten | | --- | --- | | `session_chunks.text` | Ersetzen, `chunks_fts` mit `delete` (alter Text) und neuem `INSERT` nachgeführt | | `session_chunks.raw` | Ersetzen auch in JSON-escapter Schreibweise (der Vergleich läuft auf den dekodierten Zeichen: Und-Zeichen als Unicode-Escape, `\"`, `\\`, Surrogatpaare, beide Hex-Schreibweisen); `raw` bleibt gültiges JSON, ein Treffer beginnt nur innerhalb eines JSON-Strings und enthält nie ein unescaptes Anführungszeichen, eine Zahl oder die Struktur wird nie ersetzt | | `chunk_index`, `sess_fts` | neu aus dem geschwärzten `raw` abgeleitet (Denkblöcke, Werkzeugeingabe, Werkzeugergebnis), `delete` mit den gespeicherten Feldern | | `sessions.title` | neu aus dem geschwärzten Chunk abgeleitet, übrige Titel tabellenweit geschwärzt | | `knowledge_evidence.quote`, `request_sightings.quote` | Zitate geänderter Chunks wie bisher; neu: alle Zitate tabellenweit, auch wenn ihr Chunk nicht mehr da ist | | danach | `optimize` beider Volltextindizes, `secure_delete`, WAL-Checkpoint `TRUNCATE` wie bisher | **Nur gezählt, nicht geändert** (Bericht: Zeilen je Tabelle und Nummern der Einträge): `knowledge`, `knowledge_versions`, `requests`. Ein sauberes Ersetzen gibt es dort nicht: derselbe Text steht auch in `knowledge_versions` (Verlauf), `search_documents` und beiden Volltextindizes, in Kriterien, Aktivitäten, Erklärungen, Koordinationsnachrichten, Dokumenten und in den unveränderlichen Schnappschüssen (Trigger verbieten `UPDATE`, kein Löschweg). Ein Teil-Ersetzen ließe den Rest als falsche Sicherheit stehen; was mit diesen Texten geschieht, entscheidet der Mensch. **Regeln.** - Ein Wert passt in der angegebenen und in der kleingeschriebenen Schreibweise (die FTS-Spalten sind case-insensitive, Fragmente stehen in Ausgaben klein); andere Schreibweisen nicht, dafür eine eigene Zeile. Länger vor kürzer. - Werte unter 4 Zeichen werden abgelehnt (Exit 2), ebenso ungültiges UTF-8, Steuerzeichen, eine leere Liste und ein Wert, der Teil eines Platzhalters (`[REDACTED:…]`) ist (sonst ersetzte ein zweiter Lauf den Platzhalter wieder). - Die Datei ist selbst ein Geheimnis: Gruppe oder andere dürfen sie nicht erreichen, sonst Exit 2 mit Hinweis `chmod 600` (der Modus wird am geöffneten Handle gelesen). Das geschieht vor dem ersten Zugriff auf die Datenbank. - Ein Wert erscheint nie in Ausgabe, Logs oder Fehlermeldungen (nur Zeilennummern, Gründe und Zählungen) und gelangt nie in eine SQL-Anweisung. - Der Abgleich läuft in Go auf den Zeilen, die der Lauf ohnehin liest, nicht mit `instr` oder `MATCH` in SQL: `raw` trägt Inhalt, der in `text` fehlt, und der Wert steht dort auch escapt, was `instr` nicht findet. - Die Beschriftungsregel läuft weiter, aber nach den bekannten Werten: `Passwort: <wert>` wird `Passwort: [REDACTED:literal]`. **Korrektur der Hinweise.** Der Schlusshinweis nennt nur noch `ctx db vacuum --db <pfad>` (Dienst gestoppt). `local-server.md` sichert eine laufende Datenbank jetzt mit `sqlite3 … ".backup '…'"` (kopiert Seiten und wertet keine CHECK-Bedingung aus; gegen eine mit `ctx` angelegte Datenbank mit sqlite3 3.53.4 geprüft, `VACUUM INTO` scheitert dort und lässt eine leere Zieldatei zurück) und prüft die Kopie mit `ctx db check`. `deploy/README.md` prüft eine kopierte Datenbank ebenfalls mit `ctx db check` statt `PRAGMA integrity_check` und beschreibt den neuen Schalter. **Messung** (`TestRedactSessionsLiteralsScale`, nur mit `GHOSTTREE_REDACT_SCALE_CHUNKS=200000`, in CI übersprungen; 200.000 erfundene Chunks, 1,6 GB, 800 davon mit den erfundenen Werten in fünf Formen): | Lauf | Dauer | | --- | --- | | Probelauf, nur Beschriftung | 11,6 s (17.300 Chunks/s) | | Probelauf, mit Werten | 13,6 s (14.700 Chunks/s), +17 % | | echter Lauf, 800 Chunks geändert, mit `optimize` und Checkpoint | 33,3 s | | zweiter Lauf | 13,1 s, 0 Änderungen | Danach liefert `MATCH` auf alle Fragmente in `chunks_fts` und `sess_fts` nichts, `integrity_check` und FTS-`integrity-check` sind ok. Der Literal-Scan allein schafft rund 1,2 GB/s (`BenchmarkLiteralsApplyRaw`), die Beschriftungsregel rund 160 MB/s. ## Verification - `gofmt -l .`: leer. `go vet ./...`: ok. `go build ./...`: ok. - `go test ./internal/redact/... ./cmd/ctx/...`: ok (`internal/redact` auch mit `-race`; `cmd/ctx` mit `-race` vor dem Rebase auf #149 ok). - `go test ./internal/store/... -timeout 30m`: alles ok bis auf `TestRuntimeWriterCrashRestartMatrix` („child did not reach crash boundary“), der unter Last bekannt flaky ist; er scheitert unter derselben Last (Load 8) auch auf unverändertem `origin/dev` (4584fb7), und die Phase `before_admission` allein läuft durch. - Tests, alle Werte erfunden: - `internal/redact/literal_test.go`: exakte und kleingeschriebene Schreibweise, länger vor kürzer, JSON-escapte Formen (Go-Encoder mit und ohne HTML-Escape, `ensure_ascii`-Stil, große und kleine Hex-Ziffern, Surrogatpaare), Zahl und Struktur in JSON bleiben unberührt, kein Treffer mitten in einem Escape, 20.000 Zufallstexte (JSON- und Text-Weg gleich, gültiges JSON, idempotent, kein Wert übrig), Format der Liste, Ablehnungen ohne Wert in der Meldung. - `internal/store/redactliterals_test.go`: Wert in `raw` (roh und escapt), `text`, Denkblock, Werkzeugausgabe, kleingeschriebenes Fragment, Titel (auch mitten im Wert abgeschnitten und ein Titel ohne Chunk), Zitate (auch ohne Chunk); danach kein `MATCH`, kein Segmentblock und keine Datei samt WAL mit einem Fragment, `raw` gültiges JSON, `integrity_check`; `--dry-run` ändert nichts und zählt wie der echte Lauf; zweiter Lauf ändert nichts; ohne Schalter bleibt alles; Wissen, Fassungen und Aufträge werden gezählt und nicht angefasst. Gegenprobe: vier gezielte Fehler im Code (Titel, tabellenweite Zitate, Kleinschreibung, JSON-Weg) lassen den Test scheitern. - `cmd/ctx/redactliterals_test.go`: Aufruf, Datei mit 0644/0640/0604/0620/0666/0660/0606 wird abgelehnt (Exit 2, Datenbank unverändert), 0600 und 0400 gehen; Wert unter 4 Zeichen, leere Liste, ungültiges UTF-8, Platzhalterteil, fehlende Datei, Verzeichnis: Exit 2; stdout und stderr enthalten nie einen Wert (roh, klein, escapt). ## Release impact - [ ] `major` — incompatible after 1.0 - [x] `minor` — compatible feature, or incompatible before 1.0 - [ ] `patch` — compatible fix - [ ] `none` — no release impact ## Checklist - [x] This pull request targets `dev` (or is an approved release/hotfix promotion). - [x] Tests cover changed behavior. - [x] I did not add credentials, private infrastructure details, or internal planning documents. - [x] User-facing behavior and the changelog are updated where needed. - [x] I have read and agree to the [Contributor License Agreement](../CLA.md), and I am authorized to make this contribution.
Merged
6 of 9 tasks
NurRobin
added a commit
that referenced
this pull request
Oct 20, 2026
…s Vorschlag (REQ-457) (#152) # Pull request ## What changed? REQ-457, Teil B des Gärtners (Teil A ist #149): Job 4, das Ledger pflegen, und Job 1, Ghost-Beschreibungen pflegen. Der Gärtner schlägt weiterhin nur vor. Alles, was er ablegt, ist eine gestagte Notiz mit harness `gardener`; geändert wird erst, wenn ein Mensch auf `/ui/review` bestätigt. Die Sessions sind unverändert schreibgeschützt (Read/Grep/Glob im Checkout, `git_log`/`git_show`, nur Loopback, `--no-mcp`); Daten über Requests und Ghosts bekommen sie nur im Prompt, den ctx baut. **Job 4, Ledger** (`internal/garden/ledger.go`, `cmd/ctx/garden_jobs.go`) - ctx holt die offenen Requests samt offenen Kriterien über die Client-API (Suche, dann je Request die Details) und gibt die fälligen (`--requests N`, Standard 8, 0 = aus; älteste zuerst) in einer eigenen Session mit eigener Standardanweisung (höchstens 8 je Session) mit. Rotation über den vorhandenen Zustand (`garden_checks` oder `--state-file`) unter einem eigenen Schlüsselraum `ledger|<projekt>`; ein Request ist wieder fällig, wenn sich seine offenen Kriterien ändern oder der Checkout weitergerückt und der letzte Blick älter als `--min-age` ist. - Belege sind Commits, Testfunktionen oder Dateien. ctx prüft jeden einzeln: der Commit existiert, liegt im Verlauf von `HEAD` und wurde von einem Werkzeug der Session ausgegeben (Squash-Commits behalten ihr `(#149)`); die Testfunktion steht in einer Datei, die die Session gelesen hat; die Datei ist versioniert und gelesen. Ohne einen gültigen Beleg wird nichts vorgeschlagen, und der Bericht nennt, was verworfen wurde. - Je Kriterium eine gestagte Notiz (`[Gärtner] Erfüllt? #<Request> Kriterium <n>: …`, Referenz `gardener:criterion:<Request>:<Kriterium>`). Die Review-Aktion **Kriterium als erfüllt vermerken** ruft `SetCriterionState` auf, dieselbe Store-Funktion wie `request_record_progress criterion_met`; der Beleg endet mit „Gärtner-Vorschlag #Notiz, bestätigt von Person“. Das Abschließen ganzer Requests schlägt der Gärtner nicht vor und hat keinen Aufruf dafür. - **Liegengeblieben:** Abschnitt im Morgenbericht, rein aus Daten, ohne Modellaufruf (auch bei `--dry-run` und `--mode api`). Grenze 30 Tage ohne sichtbare Aktivität (`--idle-days`, 0 = aus). Begründung: Arbeit am Ledger kommt in Schüben von Tagen bis wenigen Wochen; eine kürzere Grenze listet jeden Request zwischen zwei Schüben, eine längere lässt Vergessenes ein Quartal liegen. Gezählt wird, was das Gärtner-Konto sehen darf; Arbeit in privaten Sessions anderer Konten fehlt, der Abschnitt sagt das. **Job 1, Ghosts** (`internal/garden/ghosts.go`, `cmd/ctx/garden_jobs.go`) - Kandidaten: Dateien mit Zustand `stale`, `unknown` (geändert, Ausmaß unbekannt), dann `undescribed`, dann `drifted`, in dieser Reihenfolge, höchstens `--ghosts N` (Standard 8, 0 = aus). Die Frische rechnet ctx auf dem Client mit `ghost.Judge`/`HashFile` (mit dem gehärteten Git des Gärtners). Ausgelassen: Lockfiles, `*.sum`, `testdata`, Generiertes, Binäres, Lizenzen, Dateien über 150 KB (kleine Vorgabeliste im Code, dazu `ghost.IsIncidental`) und ausdrücklich leer gelassene Pfade (`ReviewedEmpty`). REQ-433 ist nicht gebaut; es zählt nur, was es gibt. Verzeichnisse sind nicht dabei, nur Dateien. - Eigenes Paket statt Anhang an die Wissens-Pakete: Dateien eines Verzeichnisses kommen zusammen in eine Session (gleichmäßig auf höchstens 8 je Session geteilt). Die Wissens-Sessions wachsen dadurch um kein Byte. Die Session bekommt die alte Beschreibung und zwei Nachbarbeschreibungen als Stilvorlage im Prompt, liest die Datei und schlägt eine neue Beschreibung vor oder sagt `keep`. - Prüfung in ctx: Pfad ist eine versionierte reguläre Datei, die Session hat sie mit `Read` gelesen, die Datei hat sich seit dem Lesen nicht geändert (Hash), Text 40 bis 3.500 Zeichen ohne Steuer- oder unsichtbare Formatzeichen, nicht der bestehende Text. - Je Datei eine gestagte Notiz (`[Gärtner] Beschreibung? <Pfad>`, Referenz `gardener:ghost:<Pfad>`) mit Text, Inhaltshashes der gelesenen Fassung und Hash der ersetzten Beschreibung. **Beschreibung übernehmen** zeigt alte und neue Beschreibung nebeneinander und ruft `PutGhostFile` auf, dieselbe Funktion wie `context_describe_file`. Ist inzwischen eine andere Beschreibung gesetzt als die, gegen die die Notiz geschrieben wurde, gibt es 409 und nichts wird überschrieben (die ersetzte Fassung bleibt wie immer in der Historie). - Herkunft: Die Ghost-Revision hat `Harness`, `SessionRef` und `Person`: `gardener`, `gardener:ghost-note:<Notiz>`, bestätigende Person. Der Ghost-Baum schreibt „vom Gärtner vorgeschlagen, bestätigt von Person“. `POST /api/ghosts` verweigert allen (auch dem Gärtner-Konto) harness `gardener` und `gardener:`-Referenzen mit 403 `gardener_reserved`; nur die Review-Seite setzt die Marke. **Sicherheit der Review-Aktionen:** Jede Aktion handelt nur auf Daten, die der Server selbst ableitet: Nummern oder Pfad aus der Referenz einer Notiz, die nur das per `ctx account set-gardener` gesetzte Konto geschrieben hat (Kontonummer aus `knowledge_authors`) und die niemand bearbeitet hat (leere Historie); der Titel muss dasselbe nennen wie die Referenz (Request und Kriteriumsnummer, bzw. Pfad); Beleg oder Text samt Hashes kommen aus der Notiz und werden erneut auf eine strenge Form geprüft (der Server hat keinen Checkout); die Rechte kommen vom Betrachter. Der Request, der den Knopf drückt, trägt nur das CSRF-Token; Formularfelder werden nicht gelesen (getestet mit gefälschten Feldern). Lesen und Arbeiten am Request werden vor dem Vergleich von Titel und Projekt der Notiz geprüft, damit eine Notiz, die auf einen Request eines verborgenen Projekts zeigt, wie eine unbekannte Nummer antwortet (404, getestet). **Optionen und Standards:** `--requests 8`, `--ghosts 8`, `--idle-days 30`; `--max-usd 3` bleibt der Deckel je Lauf. Die beiden neuen Jobs laufen vor den Wissens-Sessions; eine Session von ihnen darf höchstens ein Viertel des Laufs ausgeben, ihr Scheitern stoppt den Wissens-Job nie (nach zwei fehlgeschlagenen Zusatz-Sessions entfallen die übrigen). Hilfe von `ctx garden`, `CHANGELOG.md` (Unreleased) und `deploy/README.md` sind aktualisiert. **Mehrkosten je Nacht (Schätzung, nicht gemessen):** Gemessen ist der erste Nachtlauf mit 120 Einträgen in 4 Sessions: 1,27 $, also rund 0,32 $ je Session. Die beiden Zusatz-Sessions (8 Aufträge, 8 Dateien) bestimmt wie dort die Zahl der Werkzeugaufrufe; ich erwarte je 0,25 bis 0,40 $, zusammen etwa 0,5 bis 0,8 $, im Mittel also knapp 2 $ je Nacht, solange der Bestand noch durchläuft. Harte Obergrenze: je Zusatz-Session `--max-usd`/4 = 0,75 $, zusammen 1,5 $; den Wissens-Sessions bleiben dann mindestens 1,5 $, mehr als die gemessenen 1,27 $. Ist nichts fällig (alles angesehen, nichts geändert), laufen keine Zusatz-Sessions und die Mehrkosten sind null. Gemessen habe ich nur die Prompts (`--dry-run` listet sie je Session): Standardanweisung Ledger 2.056 und Beschreibungen 1.641 Zeichen, Aufgabe für 8 Aufträge mit je 4 Kriterien und langen Texten etwa 15.900 Zeichen, für 8 Dateien mit je 1.500 Zeichen alter Beschreibung etwa 15.400 Zeichen (grob ein Drittel davon in Tokens). Die Wissens-Sessions: 0 Byte Zuwachs. Die Probleme „flach geprüft“ der beiden Wissens-Sessions betreffen diesen PR nicht; die Zusatz-Sessions verlangen im Prompt mindestens eine Suche je Kriterium und das Lesen jeder Datei, und der Bericht warnt wie bisher bei weniger Werkzeugaufrufen als Einträgen. **Grenzen:** Nur Dateien, keine Verzeichnisse (`dirchanged`). Das Gärtner-Konto ist Mitglied und darf damit technisch Kriterien setzen und Beschreibungen schreiben; der Server kann ihm das nicht verweigern, ohne Mitgliedern das Recht zu nehmen. Garantiert ist es durch das Programm: `gardenStore` hat keinen solchen Aufruf, und der Herkunftstest zeichnet jeden schreibenden Aufruf eines Laufs gegen den echten Server auf und erlaubt nur gestagte Notizen, die Registrierung im Raum und Threads; die Gärtner-Marke auf Ghosts verweigert der Server allen. Ein echter Lauf mit Claude Code ist hier nicht gemacht worden (die Sessions sind im Test durch ein Stand-in ersetzt, das durch den echten Server und den echten Store läuft). ## Verification - `gofmt -l .` leer, `go vet ./...`, `go build ./...` ohne Befund. - `go test ./internal/garden/... ./cmd/ctx/... ./internal/client/... ./internal/ghost/... ./internal/server/...`: grün. - `go test ./internal/store/... -timeout 30m`: grün (935 s). - `go test ./internal/web/... -timeout 55m`: der erste Lauf fand genau einen Fehler (`TestCatalogTextsAreEnglishAndNotEmpty`: zwei neue Meldungen mit mehreren Verben ohne nummerierte Argumente), korrigiert; der vollständige Lauf auf dem Endstand ist grün (1.180 s). - Neue Tests: Validierung (gefälschter Commit, nicht im Verlauf von `HEAD`, nicht von der Session gesehen, nicht vorhandener Test, nicht vorhandene oder nicht versionierte Datei, Pfad außerhalb des Checkouts: verworfen), Rotation, Liegengeblieben, Auswahl und Prüfung der Ghosts, Zusatz-Sessions im Agent-Lauf (Reihenfolge, eigene Anweisung, Teilergebnis, Scheitern, Manipulation des Checkouts, Trockenlauf), Herkunftstest erweitert um Job 1 und 4 (weiterhin kein ändernder Aufruf, 7 statt 5 Notizen), Review-Aktionen mit Lead, Owner, Reviewer, Mitglied, Gast und Fremdem, gefälschte Notizen (anderer Autor, bearbeitet, Titel, Referenz, Beleg- und Texteform), Formularwerte ohne Wirkung, verborgene Projekte, `POST /api/ghosts` mit Gärtner-Marke. Mutationsproben: Beleg- und Verlaufsprüfung, Titelprüfung und Überschreibschutz einzeln ausgebaut lassen je einen Test fehlschlagen. ## Release impact - [ ] `major` — incompatible after 1.0 - [x] `minor` — compatible feature, or incompatible before 1.0 - [ ] `patch` — compatible fix - [ ] `none` — no release impact ## Checklist - [x] This pull request targets `dev` (or is an approved release/hotfix promotion). - [x] Tests cover changed behavior. - [x] I did not add credentials, private infrastructure details, or internal planning documents. - [x] User-facing behavior and the changelog are updated where needed. - [x] I have read and agree to the [Contributor License Agreement](../CLA.md), and I am authorized to make this contribution.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull request
What changed?
REQ-457, Teil A von zwei: Job 3 (Konsolidieren), Job 5 (Regressionsschutz) und der Herkunftstest (Kriterium 3). Teil B (Job 4 Ledger, Job 1 Ghosts) ist nicht angelegt.
Grundregel unverändert: Der Gärtner schlägt vor, ein Mensch entscheidet. Alles, was er schreibt, trägt Harness
gardenerund Confidencestaged; jede Änderung an anderen Einträgen ist ein Knopf auf der Review-Seite, den ein Mensch mit den nötigen Rechten drückt.Duplikate zusammenführen (Job 3a). Eine Duplikat-Notiz auf
/ui/reviewhat jetzt „Zusammenführen“. Die Karte zeigt vorher, welcher Eintrag bleibt (der neueste) und welche ersetzt werden. Der Knopf ruft dieselbe Store-Funktion wiecontext_supersede(SupersedeKnowledge) mit dem Grund „Gärtner-Vorschlag #, bestätigt von “ und schließt die Notiz als bestätigt. Er wird abgelehnt (409), wenn ein Eintrag nicht mehr gilt, die Scopes verschieden sind oder der bleibende Eintrag noch nicht vertrauenswürdig ist. Die Duplikat-Notiz hat kein einfaches „Bestätigen“ mehr, und „Alle freigeben“ überspringt sie. Verwerfen bleibt wie vorher.Widersprüche als Thread (Job 3b).
--proposeöffnet je validiertem Widerspruch einen Thread statt einer Notiz: verknüpft mit allen betroffenen Einträgen, Titel als Frage mit „[Gärtner]“, erster Beitrag mit Begründung und wörtlichen Zitaten, geschrieben vom Konto des Gärtners. Der Client konnte das schon (CreateThread,LinkThread,ThreadsForObject,SendCoordMessage); ergänzt habe ich nur die schmale Schnittstellegarden.ThreadSinkohne jeden Aufruf, der einen Thread-Zustand setzt. Der Gärtner meldet sich dafür als Agentgardener:<projekt>im Projektraum an, weil Threads ohne Web-Sitzung nur so lesbar und eröffnbar sind. Kann ein Thread nicht geöffnet werden, entsteht wie bisher eine gestagte Notiz.Idempotenz der Threads. Die Frage des Threads enthält eine Zeile „Stand der Einträge beim Fund: #12 (hash), #15 (hash)“ (
HashOf, Wortlaut der Einträge). Vor dem Öffnen werden die Threads an allen Einträgen gesucht:Regressionsschutz (Job 5). Im selben Paket, ohne zusätzliche Sessions, bekommen offene Pitfalls (ohne Urteil in
context_regression_cover, ohne stehenden Vorschlag des Gärtners, höchstens 8 je Session) eine Zusatzfrage: ein vorhandener Test (Datei und Name;func TestXmuss in einer getrackten_test.go-Datei stehen, die die Session gelesen hat; bei anderen Sprachen der Name als Wort), eine Skizze (Datei, Name, 3 bis 6 Zeilen; Datei-Verzeichnis muss existieren, der Name darf es noch nicht geben) oder „nicht testbar“ mit Grund. Was der Checkout nicht belegt, wird verworfen und im Bericht genannt. Je Antwort entsteht eine gestagte Gärtner-Notiz. Auf der Review-Seite setzen „Als abgesichert vermerken“ (vorhandener Test) und „Als nicht testbar vermerken“ überSetRegressionCovercoveredmit Testname bzw.not_applicable, mit dem Recht zu verifizieren (wie das Regressions-Werkzeug); Skizzen haben nur Bestätigen/Verwerfen und stehen zusätzlich in einem eigenen Abschnitt „Testskizzen“ im Morgenbericht (--out). Das Antwortformat wächst nur um das optionale Feldregression.Herkunftstest (Kriterium 3).
cmd/ctx/garden_provenance_test.golässt den Gärtner im Agent-Modus mit einem Claude-Stand-in gegen den echten Server (Store im Speicher,server.New) laufen und zeichnet jeden Schreibzugriff auf. Er belegt (a) jede Notiz hat Harnessgardenerund Confidencestagedund ersetzt nichts, (b) Thread und erster Beitrag gehören dem Konto des Gärtners und sind markiert, (c) nurPOSTauf Wissen, Raum-Anmeldung, Threads, Verknüpfungen und Beiträge, nichts, was ändert, löscht, schließt, ablöst, zurückzieht, ein Regressionsurteil oder Kriterium setzt oder einen Request abschließt (Wissen, Relationen, Auftrag danach unverändert), ein zweiter Lauf schreibt nichts Neues, ein vom Menschen geschlossener Thread bleibt geschlossen, (d) das Konto des Gärtners (Rollemember) wird vom Server für alle diese Schritte abgelehnt. Die Review-Aktionen sind ininternal/web/review_garden_actions_test.gomit Lead, Reviewer, Mitglied, Gast und Fremdem getestet; dazu ein Test, der die Methodenliste vongarden.Sinkundgarden.ThreadSinkfesthält.Rechte der neuen Review-Aktionen.
POST /ui/review/{id}/merge)context_supersede), dazu Verifizieren der Notiz. Praktisch Lead/Owner; ein Mitglied nur an eigenen Einträgen und mit Reviewrecht.POST /ui/review/{id}/cover)context_regression_cover) und der Notiz: Lead, Owner, Mitglied mit Reviewrecht.Prompt-Zuwachs. Die Frage steht in der Aufgabe eines Pakets mit offenen Pitfalls, nicht in der Standardanweisung: einmal rund 1.000 Zeichen je Session plus ein Marker je Frage. Gemessen im Test an einem Paket mit 30 Pitfalls zu je 2.000 Zeichen: 73.644 auf 74.677 Zeichen, +1,4 Prozent (bei realen Texten von 3.000 Zeichen eher +1 Prozent). Dazu kommen höchstens 8 Antworten (rund 500 Ausgabe-Tokens) und möglicherweise zusätzliche Nachschlage-Turns je Pitfall; das ist geschätzt, nicht gemessen. Die Obergrenze (
--max-usd 3, Teilergebnis bei Limit) bleibt.Herkunft der Notizen (zwei Sicherheitsbefunde der Review, Confused Deputy). Ohne weitere Prüfung hätte jedes Konto per
POST /api/knowledge(odercontext_remember) mitharness: gardenerund passendersession_refeine Notiz anlegen können, die wie ein Gärtner-Vorschlag aussieht; die Karte hätte ein anderes Ziel gezeigt, als „Zusammenführen“ bzw. „Als abgesichert vermerken“ trifft. Gelöst so:org_state(vorhandene Tabelle, keine Schemaänderung), und nur der Betreiber des Server-Hosts setzt sie, mitctx account set-gardener <name|person:N|N> --db <pfad>(wie die übrigenctx account-Befehle: Datenbankzugriff statt Token). Der Befehl löst einmal auf und speichert die Nummer;--clearnimmt die Rolle weg,ctx account listmarkiert das Konto mitgardener=true,ctx serveloggt beim Startgardener_account. Der laufende Server liest die Einstellung bei jeder Prüfung, ein Neustart ist nicht nötig. Umgebungsvariable und Flag mit dem Namen gibt es nicht; es bleibt kein Weg über Namen, also braucht der Name auch keine Reservierung fürSetOwnNameund den Provider-Abgleich (das Umbenennen auf den alten Namen weist der Store ohnehin ab). Nichts, was über HTTP erreichbar ist, schreibt die Einstellung (ein Test liestserver,web,mcpserverundclientdarauf). Konto-Nummern ändern sich nicht und werden nicht wieder vergeben (INTEGER PRIMARY KEY, es gibt keinen Löschpfad).gardenerundgardener:-Verweise (jede Schreibweise) darf nur dieses Konto mit einem Token schreiben, auch übersupersedesundPOST /api/migrated-knowledge. Alle anderen, Owner eingeschlossen, bekommen 403gardener_reserved; ist kein Konto gesetzt, bekommt es niemand, und die Meldung nennt den Befehl.knowledge_authors, nie das Label), die seitdem niemand bearbeitet hat (kein Eintrag in der Historie) und nur im Zustand gestaged/aktiv. Ohne gesetztes Konto sind die Aktionen aus.#<id>) muss dieselben Einträge nennen wie diesession_ref, sonst Hinweis „Herkunft nicht bestätigt: keine Aktion“ und 403. Die Karte zeigt die Einträge dersession_refmit ID, Titel und Link („Betrifft“ bzw. „Bleibt“/„Wird ersetzt“), nie die des Titels.ctxhat es vor dem Anlegen getan. Stattdessen: feste Form (relativer/pfad:Funktionsname, keine Leerzeichen, kein.., kein.git, höchstens 300 Zeichen), die Zeile steht ganz auf der Karte, wird so eingetragen, wie die Notiz sie hält, und die Anfrage trägt keinen eigenen Test (Zusatzfelder im Formular wirken nicht). Wer die Zeile bearbeitet, entzieht der Notiz damit die Aktion.Negativtests für jede Prüfung: Autor Reviewer/Mitglied/Lead/Owner/unbekannt, kein Konto gesetzt, anderes Konto gesetzt, Umbenennen des Gärtner-Kontos ändert nichts an seinen Rechten, ein Konto mit dem alten Namen bekommt keine (über
SetOwnNameund über direkte Namensänderung), fremder Titel, bearbeitete Notiz, 11 Formen einer unmöglichen Testzeile, Zusatzfelder, Laufzeit-Store mit Writer und ein zweiter Prozess. Mutationsproben (jede Prüfung einzeln ausgebaut, dazu eine namensbasierte Identität) lassen den jeweiligen Test fehlschlagen.Betrieb vor dem nächsten Lauf. Auf jedem Server, der den Gärtner betreut, einmal
ctx account set-gardener <Kontoname des Gärtner-Tokens> --db <Datenbank>ausführen, mit dem Benutzer, dem die Datenbank gehört (Beispiel mit transienter Unit indeploy/README.md); sonst lehnt der Server die Vorschläge mit 403gardener_reservedab. Alte Notizen ohne Autor oder von einem anderen Konto bekommen keine Aktion mehr (Bestätigen/Verwerfen bleiben).Nicht enthalten / bekannte Grenzen. Der Server erzwingt
stagedfür das Konto des Gärtners selbst nicht; das hält der Befehl ein und der Herkunftstest belegt es. Das Konto sollte die Rollememberhaben, nicht Lead.Verification
Ergebnisse: siehe Kommentar unten. Manuell geprüft: Aufruf
ctx gardenzeigt die neue Hilfe; Mutationsprobe (Notiz mittrustedstattstagedschreiben) lässt den Herkunftstest fehlschlagen.Release impact
major— incompatible after 1.0minor— compatible feature, or incompatible before 1.0patch— compatible fixnone— no release impactChecklist
dev(or is an approved release/hotfix promotion).