Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,19 @@ jobs:
# --locked so a drift in Cargo.lock vs Cargo.toml also fails here,
# not just at release.

# 0.8.11 (C8) — typegen drift guard. Regenerate the per-type ts-rs
# bindings from the Rust models, then fail if a frontend-declared type in
# generated.ts is missing a field the Rust model has (the recurring
# "added a Rust field, forgot generated.ts" drift — hit 4× in 0.8.x).
- name: Types — ts-rs bindings + drift guard
# C1 (plan 0.9) — typegen drift guard. `generated.ts` is now FULLY
# regenerated from the ts-rs bindings; a PR that changes a Rust model
# without running `make typegen` fails here on the diff.
- name: Types — regenerate generated.ts and fail on drift
# NOTE: this job's default working-directory is `backend` (see defaults
# at the top of the job) — paths below are relative to it.
run: |
cargo test export_bindings -- --nocapture
node ../frontend/scripts/check-types-drift.mjs
node ../frontend/scripts/assemble-generated-types.mjs
git diff --exit-code ../frontend/src/types/generated.ts || {
echo "::error::frontend/src/types/generated.ts drifted from the Rust models — run 'make typegen' and commit the result."
exit 1
}

# ── Anti-regression lints ─────────────────────────────────────────
#
Expand Down
Binary file added .pnpm-store/v11/index.db
Binary file not shown.
Binary file added .pnpm-store/v11/index.db-shm
Binary file not shown.
Binary file added .pnpm-store/v11/index.db-wal
Binary file not shown.
28 changes: 26 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,33 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [0.8.11] - 2026-07-09
## [0.8.11] - 2026-07-13

_« Socle clean » — the Phase-1 hardening batch from the 2026-07 full audit (security defaults, honest CI, operational reliability, the typegen/step-dispatch structural taxes), extended by the run-lifecycle campaign reviewed pass-by-pass with Codex: run state machine + resume, envelope contract, typegen contract, Link-header pagination, and the API/auth surface._

### Added

- **Source verification now covers multi-agent rooms.** Messages posted by agents over MCP (`disc_append`, live single-Agent appends) are linted against the discussion's project tree, exactly like streamed replies — the report shows as the usual badge in the UI, and a compact summary is echoed back in the agent's TOOL RESULT so it can self-correct unverifiable `[src:]` citations in its next message. Bulk transcript imports, User/System messages and project-less discussions are exempt; the insert is never blocked.
- **Codex gpt-5.6 model generation.** Tier suggestions now cover `gpt-5.6-sol` (frontier), `gpt-5.6-terra` (balanced), `gpt-5.6-luna` (fast) plus the 5.5/5.4 line; built-in tier fallbacks move to luna (economy) and sol (reasoning).
- **`ApiCall` steps can walk GitHub-style pagination** (TD-20260630 resolved). New `LinkHeader` pagination spec: follows the `Link: rel="next"` response header and accumulates bare top-level arrays — the two GitHub REST traits the executor couldn't handle, which silently truncated every list endpoint at one page (the root cause behind the PR-Review bot re-reviewing long PRs: reviews past page 1 were invisible to its dedup). Existing `max_pages` cap and `PAGINATION_TRUNCATED` signal apply.
- **Impossible state transitions are observable.** New `kronn::invariant` tracing target groups every "should never happen" signal: run-progress writes blocked by the status guard (with the held vs attempted statuses), batch counter bumps after the batch left Running, refused P2P fetch-file calls, agents emitting broken `---STEP_OUTPUT---` envelopes (and fence-strip salvages), and the boot-reconcile orphan probe.
- **Interrupted runs are resumable.** A run killed by a backend restart/crash (status `Interrupted`) can now be resumed — « Reprendre » button on the run, `POST /api/workflow-runs/:id/resume`, and the `workflow_resume_run` MCP tool. The resume claims the run atomically (a double-click gets one resume + one clear error), re-attaches the preserved worktree (refused if it's gone — never a silent fallback to the main checkout), replays completed steps into the template context and restarts at the interrupted step. A foreach fan-out re-runs ONLY the items not yet done, reconciled from three sources: the git `[item_id]` commit ledger, `Success` child runs (covers a child that finished right before the crash), and a durable done-set written to `run.state` after each item's durable effect — a done-set entry nothing confirms is treated as stale and the item re-runs. `Cancelled`/`Failed` stay sticky; children resume through their parent.

### Fixed

- **`cargo test` can no longer wipe the developer's real config.** Integration and lib tests exercising config-saving handlers wrote to the REAL `config.toml` (KRONN_DATA_DIR unset → platform dir): every full test run clobbered pseudo/avatar/model-tiers — and only the 0.8.11 keystore reconcile kept the encryption key alive. Tests are now isolated to a tempdir, and a write-chokepoint guard makes any future offender fail loudly instead of clobbering.
- **Backend test suite fully stabilized** (8 consecutive full runs green): remaining env races serialized, and the `data_dir_lock` flake root-caused to transient fd exhaustion under load (retry window added, real error surfaced).
- **`generated.ts` is auto-generated again** (TD-20260701 resolved). `make typegen` now really regenerates `frontend/src/types/generated.ts` from the ts-rs bindings (281 types — the hand-maintained aggregate carried 194 and drifted since June), and CI fails on any Rust-model change committed without it (`git diff` guard). The assembler reproduces the serde contract ts-rs can't see: on request-direction types, `#[serde(default)]` and plain `Option<T>` fields (omittable on the wire) become optional in TS, while response types stay strict. Three request/response types the frontend already used are now exported from their Rust source of truth, and the whole frontend type-checks against the regenerated aggregate (tsc exit 0).
- **P2P invite-code auth requires an ACCEPTED contact.** The auth-exempt federation routes (`claim-by-token`, `fetch-file`) and the F8 file-fetch client accepted any contact whose invite code was known — including `pending`/`refused` ones, which keep their code. One sanctioned predicate now decides (`authenticate_invite_code`); a non-accepted caller gets exactly the unknown-code answer (no status oracle) plus a `kronn::invariant` log line. The WS Presence auto-add-as-pending flow is untouched — pending is its purpose.
- **DB export/import no longer loses Quick Prompt history and learning rejection counters** (export format v5). `quick_prompt_versions` (the lineage behind version metrics) and `learning_rejections` (the anti-repetition threshold) were silently dropped, so a migrated instance lost its QP history and re-armed rejected learnings from zero. Both tables now round-trip verbatim; v4 archives stay importable (`serde(default)`).
- **Config import is zip-bomb safe.** The 512 MiB upload limit only bounds the COMPRESSED archive; `extract_zip` read entries without a decompressed ceiling, so a tiny highly-compressible ZIP could force an arbitrary allocation before any validation. Per-entry decompressed caps now apply (512 MiB `data.json`, 1 MiB `config.toml`, 64 KiB `recovery.key`) with a hard refusal past the cap.
- **`X-Real-IP` can no longer mint local trust on native binds.** The auth middleware trusted the header unconditionally — correct behind the bundled nginx (which overwrites it), but on a native bind axum faces clients directly, so any LAN peer could send `X-Real-IP: 127.0.0.1` and bypass auth entirely, including the destructive-endpoint gate. The header is now only honored in Docker (`KRONN_IN_DOCKER`/container markers); native trust comes from the real socket address.
- **Every DELETE endpoint now requires local trust or the token, even with auth disabled.** `DESTRUCTIVE_PATHS` listed 4 paths while dozens of irreversible deletions (projects, discussions, workflows, MCP configs, contacts…) stayed open to any LAN peer on an auth-off bind. The criterion is now the VERB — every `DELETE`, plus an inventory of destructive POSTs (`audit-runs/cleanup`, `api-call-logs/purge`, `debug/logs/clear`, `agents/uninstall`, `rtk/deactivate`, `cleanup-orphan-env`) — so future delete routes are covered by construction.
- **`cargo test` no longer opens a browser tab.** The `/api/open-url` contract test exercised the real handler, which really launched the system browser on `https://example.com` at every full test run (silent on WSL — visible on macOS). The handler now suppresses the launch inside test binaries, same chokepoint mechanism as the config-write guard.
- **A foreach step's output no longer breaks downstream template references.** The step envelope carries the child's full output — `---STEP_OUTPUT---` markers included — inside a JSON string; the extractor matched the FIRST end-marker, cut the JSON mid-string and rejected the valid envelope into the repair loop (so `{{steps.<foreach>.data.*}}` silently never resolved). End-marker candidates are now tried outermost-first. Locked by a degenerate-output corpus (CRLF, fenced JSON, truncations, emoji, quoted examples) and a no-panic mutation sweep over the extractor.
- **Ollama prompt truncation is now detected exactly.** The pre-flight chars/3 estimate is blind to token-dense content; the terminal response chunk now compares `prompt_eval_count` against the requested `num_ctx` — a prompt that filled the window logs a warning with both numbers and marks the step logs (`Ollama truncation:`), instead of silently degrading output quality.
- **Resuming a run interrupted during its FIRST step re-attaches its worktree.** Resume detection keyed on `step_results` alone missed the no-result-yet case and would have created a second worktree; `workspace_path` presence now also marks a resume. Same latent bug fixed on the gate `request_changes` path when the target is the first step.

_« Socle clean » — the Phase-1 hardening batch from the 2026-07 full audit: security defaults, honest CI, operational reliability, and the two structural taxes (typegen, step dispatch) paid before the continual-learning work starts._

### Fixed (hardening pass — post-merge adversarial review, 7 reviewers over the whole codebase)

Expand Down
9 changes: 3 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,9 @@ check:
typegen:
@echo "$(GREEN)▸ Generating ts-rs bindings from Rust models...$(RESET)"
cd backend && cargo test export_bindings -- --nocapture
@echo "$(GREEN)▸ Checking generated.ts for field drift vs the Rust models...$(RESET)"
@node frontend/scripts/check-types-drift.mjs || { \
echo "$(YELLOW)▸ generated.ts is missing fields — add them (see backend/bindings/<Name>.ts),$(RESET)"; \
echo "$(YELLOW) or run 'node frontend/scripts/assemble-generated-types.mjs' for a full regen.$(RESET)"; \
exit 1; }
@echo "$(GREEN)▸ generated.ts is in sync with the Rust models.$(RESET)"
@echo "$(GREEN)▸ Assembling frontend/src/types/generated.ts from the bindings...$(RESET)"
@node frontend/scripts/assemble-generated-types.mjs
@echo "$(GREEN)▸ generated.ts regenerated — commit it with your Rust model change.$(RESET)"

## Run backend tests (skips ts-rs exports to avoid overwriting generated.ts)
test-backend:
Expand Down
2 changes: 1 addition & 1 deletion README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

**Prompts plus petits, code déterministe quand c'est possible : moins d'hallucinations, facture tokens divisée, écoconception par conception.**

> **Statut : 0.8.8.** Fonctionnel mais pré-1.0. Les versions mineures peuvent introduire des breaking changes ; les patch versions sont safe.
> **Statut : 0.8.11.** Fonctionnel mais pré-1.0. Les versions mineures peuvent introduire des breaking changes ; les patch versions sont safe.
> **Licence : AGPL-3.0.** Utiliser Kronn localement pour développer *ton propre* produit ne déclenche pas le copyleft ; il ne s'applique que si tu redistribues une version modifiée à d'autres. Voir [Notes sur la licence](#notes-sur-la-licence-agpl-3-0).

## Sommaire
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

**Smaller prompts, more code where code is enough: fewer hallucinations, lower token bill, eco-design by default.**

> **Status: 0.8.8.** Functional but pre-1.0. Breaking changes happen between minor versions; patch versions are safe.
> **Status: 0.8.11.** Functional but pre-1.0. Breaking changes happen between minor versions; patch versions are safe.
> **License: AGPL-3.0.** Using Kronn locally to build *your own* product is fine; the copyleft only kicks in if you distribute a modified Kronn to others. See [License notes](#license-notes-agpl-3-0).

## Contents
Expand Down Expand Up @@ -248,7 +248,7 @@ Three modes, defaulting to `warn`, picked in **Settings → Sourcing &amp; Anti-

- **`off`** — no directive, no checking. The pre-0.8.7 behaviour.
- **`warn` (default)** — each agent receives a sourcing directive : *"cite a `file:line`, URL, or `user-confirmed` for every non-trivial claim; if you can't, say so plainly and verify before asserting."* Kronn then mechanically inspects each `[src: …]` citation the agent emits — path-jailed to the project root (no FS escape), checks the file exists and the line range is in bounds. A non-blocking per-message pill surfaces problems : **red** for fabricated citations (the cited file/line does not exist, or the source type is `training-data` = model's prior knowledge, auto-rejected), **amber** for confident claims left without any anchor. Clicking the pill opens a detail panel listing the bad citations.
- **`enforce` (preview · 0.8.8)** — same as warn today ; in 0.8.8 will refuse writes to `AGENTS.md curated="ai"` sections whose citations don't resolve.
- **`enforce` (0.8.8)** — warn, plus refuses writes to `AGENTS.md curated="ai"` sections whose citations don't resolve.

**Honest by design** : `verified` means the citation *exists*, not that the claim is *true* (catching "real but irrelevant" citations is the job of a future LLM-judge layer). The pill is non-blocking — it's a signal you decide to act on, not a hard gate. Language-agnostic + compression-proof + ungameable : you can't fabricate a file or line number that actually exists in your repo.

Expand Down
30 changes: 29 additions & 1 deletion backend/scripts/disc-introspection-mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,24 @@
"required": ["workflow_id", "run_id"],
},
},
{
"name": "workflow_resume_run",
"description": (
"Resume an INTERRUPTED run (backend restart/crash killed it "
"mid-flight). Continues from the step after the last completed "
"one, re-attached to the preserved worktree; a foreach step "
"re-runs only the items not yet done. Refused when the run is "
"not Interrupted, is a sub-workflow child (resume the parent) "
"or a batch, or when its worktree is gone."
),
"inputSchema": {
"type": "object",
"properties": {
"run_id": {"type": "string"},
},
"required": ["run_id"],
},
},
{
"name": "qp_list",
"description": (
Expand Down Expand Up @@ -1194,7 +1212,7 @@
"api_headers": {"type": "object", "description": "Extra request headers. NEVER pass auth — Kronn injects per the plugin spec."},
"api_body": {"description": "JSON body for POST/PUT/PATCH (object/array). String leaves can contain `{{var}}` placeholders."},
"api_extract": {"type": "object", "description": "Optional JSONPath extract spec: `{path: \"$.items\", fail_on_empty: false}`."},
"api_pagination": {"type": "object", "description": "Optional pagination spec (page/offset/cursor strategies)."},
"api_pagination": {"type": "object", "description": "Optional pagination spec — internally tagged `{\"type\": ...}`: Auto | Offset | Cursor | Page | LinkHeader (GitHub-style bare array + `Link: rel=next` header; fields page_size_param/page_size/max_pages)."},
"api_timeout_ms": {"type": "integer", "description": "Optional per-call timeout in ms. Defaults to plugin default."},
"api_max_retries": {"type": "integer", "description": "Optional retry count on transient HTTP errors."},
"variables": {
Expand Down Expand Up @@ -2934,6 +2952,15 @@ def call_workflow_cancel_run(args):
return _unwrap(_http("POST", f"/api/workflows/{wid}/runs/{rid}/cancel"))


def call_workflow_resume_run(args):
"""Resume an Interrupted run — atomic claim backend-side, so a double
call gets one resume + one clear error."""
rid = args.get("run_id")
if not rid:
raise RuntimeError("workflow_resume_run: missing required 'run_id'")
return _unwrap(_http("POST", f"/api/workflow-runs/{rid}/resume"))


def call_workflow_update(args):
"""Patch an existing workflow. `UpdateWorkflowRequest` is already a
TRUE patch backend-side (any omitted field preserves its current
Expand Down Expand Up @@ -3947,6 +3974,7 @@ def call_workflow_step_schema(_args):
"workflow_runs": call_workflow_runs,
"workflow_run_get": call_workflow_run_get,
"workflow_cancel_run": call_workflow_cancel_run,
"workflow_resume_run": call_workflow_resume_run,
"qp_list": call_qp_list,
"qa_list": call_qa_list,
"mcp_list": call_mcp_list,
Expand Down
Loading
Loading