Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Changelog

All notable changes to Kronn are documented in this file.

Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Release notes for 0.9.3 and earlier are available in the
[legacy archive](docs/releases/CHANGELOG-legacy.md).

---

## [Unreleased]

### Added

- Media: an image or a video can be generated from several source images of
  the discussion, ordered and removable in the Creator. Each saved image/video
  model (ComfyUI included) has a reference limit in Config: empty uses what
  the provider advertises, zero turns references off, a number declares an
  unknown limit or lowers the advertised one. The Creator, `agent_list` and
  the generation preflight read the same capability. Local ComfyUI takes
  sources through SDXL img2img (1), FLUX.1 schnell + Redux (3), FLUX.2 Klein
  4B distilled (4) and Wan 2.1 VACE (3), and checks the nodes and model files
  each workflow needs before queueing. A source image must start like a PNG,
  JPEG, WebP or GIF, and one request's sources stay under 64 MB together
  (KT-1403, #265).
- A workflow Agent step on an HTTP or local agent (Ollama, LiteLLM,
  OpenRouter…) can now generate an image or a video with `media_generate`
  and follow the job with `media_job_status`, as in a discussion: same
  validation, same per-request billing guard, same connection resolution.
  The asset lands in a room opened for the run (listed with it) and, once
  the job completes, as a file in the run's artifacts directory
  (`asset.artifact_path`) that a later step can read. The step waits with
  `wait_secs` instead of being woken. Every other task or orchestration tool
  stays refused in a workflow step (KT-1605, #502).
- Reference images work in a workflow step: a picture of the run's rooms or the
  project's discussions (a brand image a launch variable names), an earlier
  step's generation by job id or artifact path, or a picture file inside the
  project. Nothing of another run or project is reachable. A run is also capped
  at 12 generations (`KRONN_WORKFLOW_MEDIA_JOBS_PER_RUN`, at most 100), on top of
  six per step, and a dry-run Test no longer bills. A reference that matches
  nothing gets the one scope refusal (KT-1605, #502).

### Security

- rusqlite 0.41 bundles libsqlite3-sys 0.39, past the vulnerable SQLite of
  RUSTSEC-2026-0459 (KT-1620).
- The copy of a generated asset into a run's artifacts directory is written
  through the directory's file descriptor, so a link an Exec step planted at
  the asset's name is replaced, never written through. A press of a step's
  Test button is its own scope for the media guard, never shared across
  workflows or projects (KT-1605, #502).
- A project file used as a reference is read through directory fds: a link on its
  path is refused, a FIFO cannot block the open, and it must be a picture by its
  first bytes, not by its extension (KT-1605, #502).
- Every copy of `config.toml` (migration backup, snapshots, the set-aside of an
  unreadable config, a retired key) is encrypted with the instance key, clear
  copies left by earlier versions are encrypted at the next start, and the
  set-aside never follows a link or replaces an existing file (KT-1528).
- Writes guarded by `fs_guard` (docs migration, templates, audits, skills,
  worktrees) resolve through directory file descriptors with `O_NOFOLLOW`, so a
  parent folder swapped for a link after the check never lets a write leave
  the project; Windows keeps the check-then-write by path (KT-1595).

### Fixed

- Windows: Kronn could not start after an upgrade with a pending migration ("Access denied (os error 5)"), and scheduled backups failed. The backup copy was flushed through a read-only handle, which Windows refuses; it is now opened for writing first, and the Windows CI job runs the test (KT-1625, #509).

- An interrupted task execution no longer stays stuck on "recovery decision
  already consumed": it can always resume or deliver, and a resume never
  consumes the decision of a newer interruption (KT-1537, #492).

## [0.15.2] - 2026-11-08

### Added

- Trust levels: say "I trust my agents here" at the scope you choose, with the
  risks spelled out, instead of approving every step. Three levels (Standard,
  Trusted, Full access) set for the instance (Settings › Trust levels), a
  workflow or a room; the narrowest explicit setting wins. Under Trusted, an
  agent-written Quick Exec or workflow Exec step that passes values to a CLI
  is active at once, a room's request to read or plan in another of your
  projects is granted, and integration validations may reach Docker; Full
  access also runs validations without the sandbox. Choosing a level opens a
  risk panel listing exactly what changes and needs an explicit
  confirmation; an active level shows a badge on the room, the workflow and
  the settings, revocable in one click, and every approval it grants is
  journalled ("granted by trust level X"). Everything a level approves runs
  under an OS sandbox (macOS Seatbelt, Linux bubblewrap) that hides Kronn's
  own data (database, encryption key, recovery key, configuration, backups)
  from those commands and leaves the network, the API port and the rest of
  your machine open, as in your terminal; a host with no sandbox refuses Trusted and Full
  access with the reason. Raising a level needs a code from an authenticator
  app (TOTP, any app; ten single-use recovery codes; enrolled in Settings ›
  Security), so a command an agent approved cannot raise its own level, even
  from this machine; lowering never needs it. Lowering a level takes back
  the approvals it granted and the journal lists them. The risk panel says
  what stays reachable (project databases, the network, Docker, other
  repositories, the credentials of other tools) and what stays closed. The
  journal keeps 10 000 rows or 180 days. No level lets an agent change a trust
  setting, skip a delete confirmation, or lends its trust to a P2P peer, and a
  room that edits a workflow never takes that workflow's level. Elevation
  cards keep refuse / this conversation / this project and now point to the
  global setting. Code write on other projects and host access for agents are
  in the policy table for Full
  access; their gates ship with their own tickets (KT-1492, builds on
  KT-1490, KT-1474, KT-1433).
- The Page header shows the HTML weight: green up to 1 MB, orange up to 4 MB,
  red above, with advice on how to optimize. The Pages list badges heavy Pages,
  and a heavy write returns a notice the agent receives as `kronn_notice`
  (#252).
- The review-round limit is configurable and reaches every launch path (KT-1345).
  Settings > Workers sets the default (5 out of the box, or Unlimited with a
  token-burn warning), readable over `GET /api/config/workers` and, for MCP
  principals, as `default_max_review_rounds` in `task_exec_prepare`. The
  campaign dialog is pre-filled with it, single-task MCP launches take an
  optional `max_review_rounds` (integer or `"unlimited"`; before, they were
  stuck on 3), and workflow-delegated subtasks use it when the step sets none.
  An unknown MCP argument is now refused instead of silently ignored, and an
  existing execution's limit is raised with
  `task_exec_reassign({extend_review_rounds})` (or the execution card's
  « Extend rounds »), which also re-offers the findings of an execution the
  limit had escalated. With unlimited rounds the campaign's `token_budget`
  stays authoritative: once its consumed tokens reach it, a `request_changes`
  escalates with `token_budget_exhausted` instead of scheduling another
  correction, a worker reassignment is refused, and an extension re-offers
  nothing. See `docs/operations/worker-review-rounds.md`.
- A room can be granted `code` on another registered project, where Claude
  Code edits that project's checkout like its own (KT-1491, #411). The levels
  are now `read`, `plan` (what `write` always was: Kronn's planning tools,
  never the files) and `code`; a stored `write` is read as `plan` (migration
  285). A `code` grant never opens credentials or anything that runs later:
  secrets stay closed at every level, `.git`, the hooks path, `.claude`,
  `.envrc` and agent configs are denied, and a checkout that holds the Kronn
  data directory is refused. `code` is enforced for Claude Code only, and
  full-access agents are told so. Granting it needs the strong confirmation
  when enrolled; revoking it, or lowering the trust level, stops the room's
  running turn.
- Kronn checks the live database schema against its migrations at boot
  (KT-1483). A missing table, index or trigger is recreated (empty), a missing
  column is added back when SQLite allows it, and the rest is logged and
  listed under `schema` in `GET /api/health` (`ok`, `repaired` or
  `degraded`). See `docs/gotchas/schema-drift-boot-check.md`.
- The prompt limit follows the target model's context window instead of a
  fixed 100 KB, under a configurable ceiling (`server.max_prompt_bytes`,
  4 MB by default), on create, send and revise. The refusal names the limit
  and the model and suggests attaching a file or a larger-context model
  (KT-1529).
- Discussion and Artifact pages share one full-height side panel with a
  vertical rail (KT-1532). Their headers share one shell: ids back next to
  the title, a primary action and « Détails » pair, the Artifact details as a
  side-panel tab, Sync in the Invite slot and a live « Sync the N workflows »
  count (KT-1530, KT-1344). The discussion panel has a single close button
  and waits instead of opening under the mobile sidebar.
- The Automation list has a « Désactivés » filter, exclusive with « Actifs »
  (KT-1264).

### Changed

- An agent can now save a Quick Exec or an Exec line that passes run values to
  a program Kronn does not model (`fastly purge --key {{surrogate_key}}`): it
  is stored inactive, the save response says a human activates it, and the
  agent carries on instead of stopping. The run keeps refusing it until then.
  Shells, paths, wrappers and interpolation inside inline code are still
  refused at save. A human activates it with one click on the room's card, in
  « À valider », on a Page or discussion action, or on the workflow's
  readiness list; the notice shows the exact line and sends exactly that line,
  so a line an agent rewrote in between is refused and shown again; each argument is shown quoted, so two different argument lists never read alike; the activation checks and writes the line in one atomic step and never overwrites a concurrent edit. « Refuser »
  is kept server-side (migration 277). Activation and refusal are the human's
  only: a bridge token, or a request carrying the MCP bridge's `X-Kronn-Agent`
  marker (a CLI launched from the host, whose room is restored before its first save after a reconnect), cannot do either, and that marker now
  also keeps the other human-only routes and the Quick API access policy from
  treating such a CLI as a person (KT-1490).
- Live Pages no longer refuse HTML above 1 MB. A Page may weigh what its
  author wants; a 24 MiB safety guard only stops a runaway writer, and create,
  update and import apply the same rule. Artifact bundles go from 16 to
  64 MiB and the bridge buffer of Kronn-launched agents from 32 to 64 MiB, so
  a Page at the guard still exports, imports and reaches agents. The MCP
  bridge keeps its 8 MiB request limit, so an MCP agent writes a Page of up
  to about 8 MiB in one call (#252).
- Above 4 MiB, the HTML editor and the revision diff show plain text instead
  of highlighting every line on each keystroke (#252).
- A Page's revision history no longer carries every revision's HTML; the
  revision you compare is loaded alone (#252).
- The backend container accepts any host user or group id from 1 to
  4294967294 (it used to fall back to 1000 above nine digits).
- The backend container starts as root only to give its `kronn` user your
  `KRONN_HOST_UID` and `KRONN_HOST_GID` and to hand its volumes to that user,
  then runs the backend and every agent as that user, never as root. The image
  is built once for everyone instead of once per user. `docker compose exec`
  into the backend now needs `-u kronn` to get the same user as the agents.
- A bare `docker compose exec backend ...` now opens a root shell (root's home,
  root-owned files): use `docker compose exec -u kronn backend ...` to act as
  Kronn.
- `make start` on a checkout runs the published image of `VERSION`, not your
  local edits: use `make start-build` to run local code.
- CI: the macOS portability leg runs on pull requests only when the tested
  modules change, and on every push to main, with a 150-minute limit
  (KT-1535, KT-1533). Every `backend/scripts/ci/test_*.py` must be wired in a
  workflow, and the MCP catalogue census test now runs (KT-1180, #286).

### Fixed

- A cut Exec stdout no longer reaches an agent silently (KT-1570). Past its
  limit, a workflow Exec step's stdout ends with
  `[TRUNCATED: kept X KB of Y KB — full output: <path>]`, the step summary
  carries a warning, and the whole stdout (scrubbed, up to 8 MiB) is kept in
  the run's artifacts directory as `{{steps.<exec>.data.stdout_file}}`.
  Collectors, Quick Exec runs and stderr keep the old suffix and write no file.
- Agents can read run artifacts again (KT-1585). The run artifacts directory
  lived in the data directory, which the vault protection (KT-1495) hides from
  every agent, so no step could open a screenshot or report it was given. It
  now lives under `KRONN_RUN_ARTIFACTS_DIR` or the platform cache directory, in
  a `kronn-run-artifacts/<key>` directory of Kronn's own; a base inside or
  containing the data directory is refused. Docker uses a new
  `kronn-run-artifacts` volume. Existing live run directories are moved at
  startup, dead ones deleted. Any Agent step can still read any run's
  artifacts; per-run confinement is KT-1587.
- A task worker can edit its project's own `.cargo/audit.toml` and
  `.cargo/config.toml`, and a refused tool call is no longer silent (KT-1541).
  Claude Code's built-in sensitive-directory check asks on any path under
  `.cargo`, even in `acceptEdits`, and a `--print` worker cannot answer: the
  Edit came back refused with no reason. The worker's settings now answer that
  one ask, for the worktree's own `.cargo` only, through a script that refuses
  any path going through a symlink or leaving the worktree (the Kronn data
  directory stays denied). A refused tool call is written into the task
  worker's transcript with its reason, the "worker ended without a delivery"
  notice repeats the last refusals of the current attempt, and so does the
  delivery summary of a worker that delivers anyway. See `docs/gotchas/claude-code-sensitive-directories.md`.
- A quota refusal no longer sticks to an execution after the reset, and no longer
  sends it to another provider behind the user's back (KT-1564, regression of
  KT-838 / KT-593). A refusal that announced its reset expires at that instant:
  the watchdog hands the execution back to the same provider, tier, model and
  profile and the escalation closes, unless its campaign is paused or a person
  moved it first. Falling back to another provider is now its own explicit
  opt-in (`quota_fallback` on the campaign, a toggle in the launch dialog;
  `quota_fallback_agents` on `task_exec_launch`), keeps the execution's tier,
  returns to the original worker after the reset even when the fallback
  provider resets first, and shows in the review card and the delivery report.
  **Migration 286 turns off the fallback that campaigns used to get implicitly
  from `allowed_agents`**: re-enable it per campaign with `quota_fallback`.
- One request no longer starts a cascade of media generations, and Stop is
  final (KT-1481). Every agent generation is tied to the human message at the
  base of its wake chain: a result or failure notification now says it is a
  notification, not a new request; after a failed, timed-out or cancelled job
  nothing more is generated for that request (no retry, no other provider, no
  dropped references — KT-1420); a request may start at most six jobs; and a
  notification only leads to a new generation that builds on its result. Stop
  disarms the callbacks of running generations in the database, so a late
  result cannot wake the stopped agent, restart included. A new human message
  is a new request with a fresh allowance.
- A task is no longer marked `done` when its delivery is integrated into a
  campaign or feature branch that has not reached the default branch yet
  (KT-1519). It stays `in_progress`, integrated and awaiting the merge; the
  planning page, the board and the discussion plan say « Intégrée dans
  <branche> — en attente de fusion ». The next task of the campaign still
  launches on top of it, and a campaign whose tasks are all integrated
  completes. The task closes when the default branch holds the integrated commit
  (local or `origin/<default>`, which the sweep fetches first), when GitHub
  reports the pull request that merged its branch, or its own `github-N` issue
  closed (a squash merge never makes the commit reachable; an issue or pull
  request the task merely links proves nothing), or when a human closes it;
  Kronn checks at boot, after each integration into the default branch and every
  five minutes (migration 283). The Planning list, the project Tasks panel and
  the discussion monitor show the wait too: the plan counters have an
  `awaiting_merge` bucket apart from `in_progress` and `done`.
- The dark-theme contrast scan no longer fails on `:root`, the name axe gives
  a node it could not address once the page re-rendered it; the Settings route
  left one such node behind on any dark theme, one run in two.
- The boot screen's fade-out tolerates a torn-down document, so a settle
  timer that outlives its test no longer throws `document is not defined`.
- Every release tag now publishes the backend and frontend Docker images
  (`ghcr.io/docroms/kronn-backend` and `kronn-frontend`, linux/amd64 and
  linux/arm64, built on native runners). From the release that ships this
  change (0.15.2), `make start` and `./kronn start` pull the image of the
  checked-out version and start it: they no longer compile the backend on your
  machine. A checkout of 0.15.1 or earlier keeps its old `make start`, which
  compiles; republishing the 0.15.1 images does not change that, so a machine
  under ~20 GB should use the desktop installer until 0.15.2. Before, a Docker install built the
  715 000-line backend crate locally, which needs about 19 GB of memory, so a
  machine with less (for example WSL with 5.8 GB) ended with `rustc` killed
  after minutes of work (KT-1468).
- `make start-build` (or `KRONN_BUILD_FROM_SOURCE=1`) builds from source; an
  image that cannot be pulled and is not on the machine yet (a `VERSION` with no
  published image, e.g. right after `make bump`) falls back to it. A checkout of
  `main` keeps the last release's `VERSION`, so `make start` runs that release's
  published image, not the local code. An image that cannot be pulled but is already
  local (an offline restart) is started as it is, without building.
  `KRONN_IMAGE_TAG` picks another published tag; quotes around it in `.env` are
  ignored and `${VAR}` references in it (`KRONN_IMAGE_TAG=${RELEASE_TAG}`) are
  resolved as Compose does, instead of being passed on literally.
- Before any source build, Kronn reads the memory Docker can use (plus
  swap). Below 20 GB, the need of the default build (19 GB peak), it stops at
  once and names the alternatives: the published image, the desktop installer
  for your platform, a bigger machine. The message carries the measured numbers
  (19 GB peak; 10.4 GB and 12 minutes with one codegen thread).
  `KRONN_FORCE_BUILD=1` builds anyway.
- The `latest` image tag follows the highest stable release only: republishing
  an older release from the workflow no longer moves it backwards. It is moved
  by its own job, one run at a time per image (a queue, nothing is cancelled
  mid-write), which points `latest` at the highest published stable release at
  that moment: runs that overlap in any order end with `latest` on the newest.
  A registry or `git ls-remote` error in that job fails the run (after a short
  retry), whichever release is the highest, instead of a green run that leaves
  `latest` unchanged; only a registry that confirms the highest release is not
  published yet ("not found", "manifest unknown") leaves `latest` alone.
- An empty `KRONN_IMAGE_TAG` in the environment wins over `.env` and means the
  `VERSION` default, exactly like Compose; only an unset one reads the file.
- A project's task count was three different numbers: the Planning sidebar,
  the project's Tasks tab and `task_list` each counted their own way. They now
  share one definition: archived tasks are out unless asked for, a subtask is
  a task, a multi-project task counts in each of its projects, and a task
  belongs to a project through a direct link or a linked discussion. The
  sidebar shows the backend's counts and lists every page, so a group lists
  the rows its count announces; "General" counts exactly the tasks with no
  project at all. The list stops after 5,000 tasks and says so. The
  per-project and General counts come with the first page of `task_list` only
  and are left out after a cursor, so an agent never reads an uncomputed 0 as
  "no task without a project". A search in the sidebar lists exactly the tasks
  the backend counted, including those matched only by their description
  (KT-1351, #237).
- Opening an audit validation from the project page (audit timeline,
  tech-debt validation or disputes link, docs check panel, a partial audit
  that re-verified unchanged sections) opens that discussion again, instead
  of the last discussion viewed, which could belong to another project
  (#421).
- A foreach child refused for quota waits and resumes through its parent
  instead of failing its item (KT-1556): 19 to 31 of 40 PR reviews were lost
  this way. The parent run goes to `WaitingQuota`, reclaims the same child at
  the refused step, and does not replay items that already failed in the same
  sweep (no duplicate PR comments). Claude's short "limit · resets …" wordings
  are classified as quota; "Context limit: reset the conversation" is not. The
  run page no longer offers Resume on a sub-workflow child.
- A quota pause no longer spends a run's Timeout guard, which now measures
  active runner time (persisted, so a restart keeps it). A reset announced past
  the old deadline schedules a wake like any other instead of leaving the run
  in `WaitingQuota` for good; runs parked that way by the old engine wake from
  a reset less than a day old (KT-1562, KT-1563).
- A quota fallback chain remembers every provider that refused, so two
  refusals without a reset escalate instead of bouncing (KT-1564).
- An undelivered worker parks as a pending resume and a woken worker adopts it
  (KT-1540).
- A burst of peer wake-ups starts one room-agent turn instead of one turn per
  message, which produced near-identical replies a minute apart (KT-1539).
- A room's native agent can follow, review, stop, resume and reassign the
  execution it launched; before, only a joined CLI session could (#241).
- An MCP join moves the session link only after its binding is saved
  (KT-1566).
- An HTTP agent past a tool's per-turn ceiling stops after a few refusals:
  within one response, refusals past 8 are dropped and the model is told how
  many; refusal-only rounds lead to a synthesis, then a hard stop; the room
  gets at most two trace lines per tool and round plus a summary (KT-1545).
- Tool calls some OpenAI-compatible models write as `###Call:name{...}###`
  text are stripped from HTTP chat replies, and a first turn made only of them
  is retried once (KT-1543, #472).
- A 401 on a cached OAuth2 or token-exchange bearer clears the entry, mints
  once more and retries once, instead of failing until the guessed hour runs
  out (KT-1067, #282).
- The Copilot host sync keeps every hand-written MCP entry, like Codex, and
  the user's Copilot or Codex config is copied to `.kronn-backup.1` before
  Kronn's first write (KT-1036, #278).
- In Docker, network exposure follows `KRONN_BIND`: Settings shows a note
  naming it instead of a switch that changed nothing and asked for a restart,
  and the route refuses with the same pointer (KT-1066, #281).
- An unknown value or key in `config.toml` no longer resets every setting
  (KT-1513). A credential this build cannot represent is never dropped: once
  the encrypted store holds the credentials it is kept in
  `config.toml.preserved.enc` (`0600`). See `docs/operations/key-management.md`.
- The "Add a project" bootstrap form lets the user pick the agent and
  reasoning tier instead of silently running the first audit-capable agent;
  the endpoint refuses with a 422 an agent the form would not offer
  (KT-1591).
- A Kronn write in flight no longer sends re-read docs back to the
  revalidation plan (KT-1590).
- Docs edited outside Kronn on a validated project are flagged on step 4,
  which offers to approve them as they are (human only, refused while a
  citation is broken) or to fix the broken citations first (KT-1489, #406).
- Audit card errors name the refused TD id and, when the central
  `inconsistencies-tech-debt.md` exists but cannot be read, its real cause
  (#258).
- Commits reachable from `origin/HEAD` are no longer checked as the worker's
  identity trailers at delivery (KT-1498).
- An external API base keeps a host named `v1` while `/v1` normalization
  stays pinned (KT-668).
- The CI verdict no longer goes red while it waits: earlier gate check runs
  on the same head are marked "Superseded", a CI run that starts after the
  verdict is accepted (KT-1230, #288), and a verdict that outlives its
  upstream run stays pending and is re-armed when CI completes, instead of
  cancelling itself red (KT-1586).
- Flaky tests fixed at the root: browser storage cleared after each frontend
  test (KT-1057), a simulated restart that erased a parallel test's warm-up
  claim (KT-1565), a disk-report check tripped by a temp dir name (KT-1567),
  the anti-hallucination mode changed only through a locked, restoring
  override (KT-1569), the OllamaCard registry re-check (KT-1588) and new-tab
  detection in the routing e2e test (KT-1589).

### Security

- Settings › Security and system tells its three security mechanisms apart
  (KT-1534). The authenticator has its own card (`#settings-authenticator`,
  between the recovery phrase and the trust levels) with numbered enrolment
  steps; it names the real `human-admin-secret` path from the server (macOS
  `~/Library/Application Support/com.kronn.kronn/`, Docker `KRONN_DATA_DIR`)
  instead of a Linux path given for every platform, says when agents can read
  that file too, and how to get a new secret (`recover-admin-secret`). The
  network access token card follows the server (`auth_token_set` on
  `GET /api/config/network-exposure`) instead of this browser's stored copy:
  after turning on network exposure it no longer says « désactivé » and offers
  to create a token that would replace the one peers use. The risk panel puts
  the missing authenticator first, links to its card and shows no
  acknowledgement until one is set up; « coffre Kronn » becomes « dossier de
  données de Kronn ». Once an authenticator is enrolled, the admin secret alone
  no longer mints a publication grant, rotates the admin secret or rotates
  another grant's secret: those routes ask for the strong confirmation too (a
  holder renewing its own grant does not; before an enrolment nothing changes;
  the `recover-admin-secret` recovery is untouched). The admin-secret hints
  (first enrolment and replacement) say Kronn generated the secret (at first
  start, or at the last recovery or rotation) and that it proves the machine
  owner; once an authenticator is enrolled, enrolling or revoking a credential,
  rotating a secret with it and replacing the authenticator also need its code
  (listing does not). The claim about who reads the file is scoped to what the
  sandbox covers: agents Kronn launches lose the data folder while its
  protection is on, a CLI you run yourself can read it, so enrol before handing
  it tasks.
- A safe plugin export no longer leaks a secret held in the plugin definition
  (KT-1040). Without values the bundle replaced only environment values and
  copied the definition whole: a token in a URL (query, path, user info, host),
  a literal header, stdio arguments, a description, a TokenExchange body or
  endpoint left in clear. The export is now an allow-list: names and env-key
  names with no token-shaped word, `${ENV.…}` references, URL origins with a
  clean host, plain endpoint paths and catalogue values stay; every other value,
  the config label and env-key names included, becomes a named placeholder
  `${ENV.KRONN_EXPORT_<FIELD>}` that the import review lists and asks for, and
  that the import puts back with its JSON type (one left empty is named in a
  warning and by the plugin's first call). Kronn's own plugin ids are kept when
  the name they derive from is, and any other id gets a fresh one shared by the
  configs of that server; a catalogue plugin round-trips with no placeholder.
  The per-plugin export masks the same way, and its file import accepts the
  placeholders and names those left; the export with values is unchanged. Hosts
  and plain lowercase paths are only shape-checked, and so are name-like values
  (names, labels, header names, scopes, the JSONPath, env-key and reference
  keys, body keys, plain-slug ids): an all-letter word up to 20 characters and a
  letter-digit word up to 8 (`OAuth2`, `GPT4`) outside a segmented run like
  `4B7D-9F1C` are kept, so keep secrets out of them or use `${ENV.KEY}`.
- Agent processes Kronn launches lose the Kronn data directory to the OS
  sandbox (Seatbelt, bubblewrap); the API port and the network stay open, and
  `recovery.key` joins the secret names (KT-1495).
- Once an authenticator is enrolled, secret and destructive routes (vault
  reset, import, token regeneration, recovery passphrase change, sensitive
  deletions) require the strong confirmation, through one gate for every
  caller. The recovery routes stay open (KT-1496).
- A bridge-token GET with a non-JSON body is refused with 415; an empty GET
  is still accepted (KT-1068). A test keeps `Own` bridge routes on discussion
  targets only (KT-1069, #283).
- `minimatch` 11.0.3 for GHSA-g3hv-75wg-m249 (ReDoS through eslint).
- The security scan is green again (KT-1533). `calamine` moves to 0.37, so `zip`
  8.6 leaves both lockfiles and RUSTSEC-2026-0429 no longer applies to the
  spreadsheet reader. The only remaining `zip` < 9 is the build-time copy
  `utoipa-swagger-ui` 9 uses to unpack its own bundled Swagger UI. The advisory is
  ignored for that path with a dated rationale in `backend/.cargo/audit.toml`, and
  moving to `utoipa` 6 is tracked separately. `calamine` then moves to 0.38
  for RUSTSEC-2026-0436: 0.37 panics or allocates without bound on a malformed
  CFB/OLE2 file (a legacy `.xls`), and Kronn reads untrusted spreadsheets in
  context files.

## [0.15.1] - 2026-10-22

### Added

- Custom skills keep a version history, like Quick Prompts. Every change
  is saved as a full copy of the skill; a save that changes nothing adds
  no version, and a skill written before this release gets its current
  state as v1 at its next save. The skill page has a 🕒 history button:
  the timeline of versions, a line diff with the previous version, and a
  restore that asks first and records the restored content as a new
  version, so nothing is lost. Agents can read the history (`skill_get`
  lists the versions, `custom-x@N` reads one) but never restore a version.
  Built-in skills have no history (KT-1434).
- Agents can create and edit custom skills, CLI agents (MCP bridge) and
  HTTP/local agents alike, with the rules of Quick Prompts: `skill_create`
  saves a skill in the room's or workflow's project, `skill_update` changes
  a skill of a project in reach, never a builtin one. Each change is a new
  version, so a step pinned to `custom-x@N` keeps what it was validated on,
  while an enabled workflow loading the bare skill is disabled by an agent's
  edit until a human enables it again. A human tries the new version on real
  runs from the workflow's shadow settings. An agent never sets a skill's
  `allowed_tools`; restoring a version and deleting a skill stay human.
- Agents know and can read what these versions and shadow runs need. CLI
  agents (MCP bridge) and HTTP/local agents get the same tools and the same
  guidance: `skill_get` lists a custom skill's versions and reads one with
  `custom-x@N`, `qp_get` lists a Quick Prompt's versions and reads one with
  `version`, `workflow_get` shows the shadow config. Both are read only and
  scoped like the object itself: a project's skill or Quick Prompt, and its
  versions, stay in that project. The step schema and the workflow guide's
  new « Versions and shadow runs » section explain `external_effect` (mark
  every step that acts outside Kronn), `{{run.variant}}` (keep the two runs'
  branches and paths apart), `{{refs.<name>}}` / `KRONN_REF_<NAME>` and
  pins; configuring or promoting a shadow stays human-only, and the agent
  points the user to the workflow editor and the Page's Resources panel. HTTP
  agents can now also author a workflow's `refs`.
- A workflow step, a Quick Prompt or a discussion can load a precise version
  of a custom skill instead of the latest: `custom-x@3` loads version 3, and
  later edits of the skill no longer change what that step runs. The skill
  pickers of the workflow editor and of the Quick Prompt form show, for each
  custom skill picked, « latest version » or a version to pin. A version
  that does not exist is refused at save, never silently replaced by the
  latest (KT-1447).
- An Agent step can also pin the version of its Quick Prompt (« Quick Prompt
  version » under the Quick Prompt picker). Left unset, the step loads the
  latest version, as before; pinned, an edit of the Quick Prompt reaches
  the step only when someone picks the new version. A repository Quick
  Prompt's pinned content is checked against its approval too, and an
  imported workflow drops the pin, since its Quick Prompt comes without
  history.
- Shadow runs: a workflow can start, alongside each normal run, a shadow run
  on the same inputs that loads other versions of its skills and Quick
  Prompts. Steps marked « acts outside Kronn » (posting, sending, writing
  elsewhere) and Gates are skipped in the shadow run, so nothing happens
  twice; it never counts against the concurrency limit, never pages on
  failure, and its sub-runs are shadow runs too. It works in its own git
  worktree, unless the workflow declares it never writes the checkout. Set
  it from the « Shadow » button of the workflow page or in the workflow
  editor (« Shadow runs »: on/off, the projects it covers, the version of each
  skill and Quick Prompt). The run list marks shadow runs, and a normal run
  shows its shadow side by side, step by step. Steps read the variant as
  `{{run.variant}}` (`normal` or `shadow`). Agents cannot set it (KT-1449).
- A Page shows the resources behind it: the Quick Prompts and skills its
  workflows use (those that publish to it, those its buttons launch, and
  their sub-workflows), with their latest version, their history, a link to
  edit them and, step by step, what the normal run and the shadow run load.
  « Promote » hands a version tested in shadow to the normal runs of that
  workflow: a skill in every step that loads it, a Quick Prompt pinned on
  the steps that load it. The Quick Prompt itself, which other workflows
  may use, is left as it is (KT-1450).
- Workflow and Page exports carry the custom skills their steps and Quick
  Prompts use: each skill's file, name, version and a content hash. On
  import, a skill identical to one already here is reused, a missing one is
  created, and a skill with the same name but other content is shown in the
  import preview, where you choose to create a copy (the default) or use the
  local skill. Built-in skills stay plain references. A skill neither here
  nor in the file is listed before the import and reported after it, never
  dropped silently. Older export files still import (KT-1446).
- Workflows can name the objects they use: a Page, workflow, Quick Prompt,
  Quick API, Quick Exec or skill gets a name in the editor's new "Named
  references" section, and steps write `{{refs.<name>}}` instead of pasting
  its id; Exec steps also get it as `KRONN_REF_<NAME>`. Saving checks the
  object exists and belongs to the workflow's project or to none. Exports
  carry the named objects and imports point each name at the imported copy;
  a file missing one of them is refused with the name it lacks (KT-1448).
- Local image and video generation through ComfyUI (KT-1381). Config ›
  Agents › Local gains a ComfyUI card next to Ollama: server state and
  version, the one-command install on macOS (`brew install --cask comfy`), and
  Image / Video pickers listing the installed models Kronn has a measured
  workflow for (FLUX.1 schnell, SDXL such as RealVisXL, Wan 2.1). Once a model
  is chosen, ComfyUI is offered wherever media is generated (new
  conversation, the Assets Creator) and in `agent_list` as `local:comfyui`,
  so agents generate on it through `media_generate`. Local generations cost
  nothing and never enter estimates; ComfyUI never appears as a chat agent.
  The card also downloads measured models (RealVisXL V5, FLUX.1 schnell, the
  Wan 2.1 1.3B pack) into ComfyUI's own model folders, with progress, cancel,
  resume and a disk-space check. Everything machine-dependent is a setting:
  the server address, the install folder (default `~/ComfyUI-Installs`), the
  workflow of a model Kronn cannot recognise on its own, and per-slot steps,
  guidance, sampler and scheduler. A stopped ComfyUI, a job it lost or one
  finished without a file fails at once with a sentence; a job that times out
  or is cancelled is cancelled on ComfyUI by its id, never by a global
  interrupt; downloads go only into the install folder of a ComfyUI on this
  machine, never through a link, and are checked against their SHA-256
  (macOS and Linux; Windows links to Hugging Face).
- A « hide orchestration messages » toggle beside « hide notes » in the
  discussion composer (KT-1330). What the orchestrator posts into a room
  (review requests, delivery summaries, terminal events, bounces, timeouts,
  worker-undelivered alerts) now carries a typed `kind: "orchestration"`
  (`messages.kind`; rows older than the column resolve it from their `orch-…` /
  `delivery-summary:…` id). Hiding folds each run of consecutive ones behind one
  expandable marker, persisted like « hide notes » (`kronn:showOrchestrationMessages`).
  A question card, a campaign gate waiting on a human, a message with an open
  action and a search hit are never folded. Display only: agents and the
  bridge/MCP read paths still get every message. Orchestration messages no
  longer raise the unread badge. See `docs/architecture/orchestration-message-kind.md`.
- Decision models are recognised (KT-1366). Kronn reads what Ollama's
  `/api/show` says each local model can do, so a decision model such as
  `clef-flash:9b` (which answers typed questions and cannot chat) is no longer
  offered as a chat or agent model, and a launch on it is refused before it
  reaches Ollama. The decision model is chosen like the tiers, with the same
  picker: a « 🧭 Décision » row on the Ollama card and a Decision slot next to
  image and video on an HTTP connection, each listing decision models only.
  A local Ollama image model (such as `x/flux2-klein:4b`), or any model
  whose declared capabilities do not include chat, is no longer offered as a
  chat model either. An OpenRouter connection lists its decision models (Perplexity Decider,
  Jev, Clef…), which OpenRouter returns only when asked for by output
  modality. The manual catalogue form offers the `decision` capability, and a pull that
  needs a newer Ollama says to update it instead of showing the raw error.
- The audit repairs more of what it used to fail on, and tells a human the rest
  (KT-1357, 18/10 audit matrix). Kronn splits a list of lines of one file
  (`deploy.yml:210,214,218`) into one marker per line, moves the comment that
  trails a `path:line` out of the marker, and names an empty file in backticks
  instead of citing it; the retry feedback for step 8 names each unaddressed
  detector signal with the exact text to write (the flagged file's path, or its
  name with the flagged line), and when the attempts are spent Kronn appends an
  `## Unaddressed detector signals` section to the tech-debt index and sends a
  `warning` instead of failing the run. The partial refresh of the TD index now
  gives a malformed coverage matrix back to the agent, as the Full audit does
  (a Node service's revalidation lost step 8 to its first attempt), and a refresh of a
  section built from the whole source tree ends `unchanged`, with no agent, while
  that tree is the one its baseline recorded; the glossary joins them (its
  baseline now records the tree). An `unchanged` partial step is stored as done.
- The audit checks what its documents claim against the repository (KT-1387,
  KT-1388, KT-1370, KT-1389; v1/v2 A/B KT-1358). A `make` target or a
  `npm`/`yarn`/`pnpm`/`composer` script a document cites that no Makefile or
  manifest defines is sent back to the step that wrote it; the commit
  conventions are written from the observed history (`git log` pattern,
  prefixes, language) and detected tooling, and called enforced only when a
  GitHub Actions step under an established `bash`/`sh` shell or a dedicated
  commit-message action runs the checker (a local hook, another CI system, a
  Windows or matrix runner is « configured »); an unfilled commits template
  fails its step. Stack facts read from lockfiles and runtime files are given
  to the prompts, versions that contradict each other across documents are
  flagged, and a debt is not declared resolved without proof in the code. A
  citation marker no attempt could repair is demoted, after the last attempt,
  to a flagged backticked path. All of these are advice measured on 754 real
  audit documents (no false positive) and never fail a run: the partial
  refresh feeds them back per attempt like the Full audit, they are recorded
  as each step ends and survive a resume, and the audit timeline lists them
  next to the unverified references. Repository files are read through the
  project-rooted reader only, and a secret file is never cited by line.
- Testing a LiteLLM connection no longer calls a valid key rejected (KT-1412).
  With no tier models, the test walks the catalogue; a model the proxy refuses
  for this key (its allow-list or tag routing, answered 401/403 with a reason)
  is a verdict on that model, so the next one is tried and the result says
  "the key works, N listed models are not allowed for it". A refusal the
  proxy does not attribute to a model still reports the key as rejected.
- The audit no longer reports committed secrets as absent, and its documents
  agree on the tests and the CI (KT-1404; A/B KT-1358 round 3). The security,
  Docker, tech-debt and final-review steps are given Kronn's own scan of the
  tracked files: private keys (by name and PEM body), Slack / Teams / Discord
  webhook URLs, well-known vendor tokens and literal values assigned to
  token / secret / password names, listed by path and kind, never a value. A
  listed path is a finding to revoke and purge whatever the file is named, and
  « no secrets » is never written while one is listed. The project index, the
  testing and operations documents and the final review are given the tracked
  test files per runner, each CI workflow's events, each job's condition and
  the runtime version each job runs; a trigger or a gate is described only as
  the workflow states it (a label-gated CI never « runs on every PR », no
  purpose the repository does not write, « blocks merge » is a repository
  setting), and the final review makes every document agree with these facts.
  The stack facts also carry the application's version and the direct
  dependencies the lock file shows old (deprecated, abandoned, or an old major
  of a short list), which the Dependencies and tech-debt steps cite; a debt
  that rests on an absence (« no response caching ») must first look for the
  mechanism in the code. These are prevention only: no new check was added.
- Arbitration cards are never lost (KT-1401). A `kronn-question` whose label,
  question or context is too long is recorded with the text shortened at a word
  (the full text kept in its description or context); a card still invalid is
  shown as such with its reason and returned to its author in the
  `disc_append` result (`invalid_cards`). Answering the last pending card of a
  workflow step that has ended wakes the room's agent once with every answer;
  an earlier card says its answer will reach that agent.
- A finished task execution gives its disk back (KT-1397). Its managed
  checkout is removed once the execution is Done, Failed or Cancelled, nothing
  uses it and it has been idle for `server.disk_sweep_idle_hours` (by finish
  time and by the newest file written in it); the branch is kept and anything
  uncommitted is saved first as two patches (staged, then working tree) under
  the execution's evidence, so the exact state can be reapplied. A cancel with
  « remove if clean » still removes only a clean checkout; a dirty or preserved
  one waits for the sweep, which runs at start-up and every 30 minutes. Lane
  build dirs (`target/wt-*`) whose checkout is gone are reclaimed when durable
  lineage attributes them to a finished execution. A state over 256 MiB, with
  unmerged paths or written recently is kept, with the reason recorded.
- A Definition of Done edited during an execution is adopted (KT-1402): the
  execution re-reads it and the worker is told the new requirements, instead of
  every delivery being refused. A refused delivery is recorded and shown in
  `task_exec_status` (`last_delivery_refusal`) and in the escalation, and an
  accepted one clears it.
- One guide for building automations, read the same way by every agent
  (KT-1341). The built-in workflow architect is served section by section
  through `tool_manual({tool: "workflow-architect", section})` to CLI agents
  (MCP) and HTTP agents alike, and gains a « Reach » section: the workflow's
  own project, the workflow scope a human sets (Projects accessible, every
  project in red), why a step never inherits its room's grants, the parent
  task over several repositories, and the general resources. HTTP agents get
  `room_scope`. A refusal on another project now names the right way out for
  its caller: `room_scope` for a room's agent, the workflow's scope for a step,
  the principal for a worker (it used to name a `scope_request` tool that does
  not exist). A test fails when a tool exists for one audience only, in either
  direction, unless it is listed with its reason; the tools HTTP agents do not
  have yet are deferred to KT-1356. The MCP catalogue does not grow.

- A full audit no longer ends Interrupted on what the audit could not fix
  (KT-1353, audit matrix: a PHP site and a Node service). The documentary blockers the
  committed documents held before the run are reported
  (`documentary_preexisting`, `phase: final_gate`) and never charged to the
  founding step, even when a step rewrites the document around them (a
  re-verification appending to its history); the validation lets them through
  only while the document is unchanged since the run. They are stored on the
  run (`documentary_notes`), listed in the audit timeline and at the top of the
  validation prompt, with the replacements Kronn applied; the blame ledger follows a
  blocker through the rewording of its message, so a bare sheet citation written
  by step 8 stays step 8's when step 10 grows the sheet. Where the gate's own
  message carries the one replacement (a link relative to the document's folder,
  the presence form of a secret file or of a tech-debt sheet, the single
  did-you-mean path, a range one line past the end), Kronn applies it before the
  step is judged and logs it (`documentary_autofix`); ambiguous suggestions are
  never applied. A citation into a git-ignored path says so instead of
  "does not exist", and the step 8 prompt shows how a sheet is cited.

- A workflow's project scope can open every project (KT-1336): read, or
  planning write behind the same typed confirmation as a room, shown in red on
  the workflow page and the launch card, pinned to the validated revision and
  withdrawn on its own. The scope editor is redone: every project in one
  searchable view with a None / Read / Write picker, the projects already
  opened first, and one « Validate the scope » action. The editor is folded to
  one line by default (KT-1355): the title, the granted access as chips (or the
  red « Every project » badge, « No project access »), and a Details toggle that
  opens the full editor. It opens by itself when a human has something to do:
  a scope never validated, or suspended by an edit of the workflow.

- A merge no longer bounces on doc citation line anchors (KT-1321). The
  integration merge, and the merge a worker finishes through `task_exec_commit`,
  stop before the merge commit: a doc conflict that differs only by the line
  numbers of `[src: file: …]` markers is resolved to the target's text, then
  every citation to a changed file the merged tree left broken is re-anchored
  by mapping its lines from the side it was written against to the merged file
  (the `#symbol` must lie in the new range; the nearest occurrence to the
  mapped position, within 20 lines and with no tie, settles a changed range;
  otherwise the merge bounces on the doc). Only `docs/` and `backend/docs/`
  are touched, from the merged index: a doc to rewrite, or a cited file, with
  unstaged work makes the merge bounce instead of sweeping that work into the
  commit. Both land in the signed merge commit. Any other
  conflict still bounces. `make docs-reanchor` also runs where no
  target-branch merge-base exists (a worker worktree) and, during an unfinished
  merge, maps from both sides of the merge without a `BASE`.
- Kronn no longer fills the disk with per-lane build dirs (KT-1318). Every
  parallel task lane builds in its own `target/wt-<lane>` (5-30 GB) and nothing
  ever removed them: 195 of them took a 1.8 TB disk to 683 MB free. Now an
  orphan sweeper (boot, then every 30 minutes) removes the build dirs Kronn
  created, whose execution is finished, with no worktree, unwritten for
  `server.disk_sweep_idle_hours` (default 6, minimum 1: `0` is read as 1, never
  as "no idle check"); a dir Kronn did not create
  (no ownership marker) and an active lane are never touched. Below
  `server.disk_launch_min_free_gib` (default 30) a worker launch or a full
  audit sweeps first, then is refused with the reason, the execution parked
  `Blocked` and an alert in the principal room. **Settings → Disk**, the
  `GET /api/maintenance/disk` report and the `disk_usage` MCP tool show free
  space and Kronn's usage by kind and by task, with a Purge action that takes
  orphans only (a named entry needs a confirmation). The `disk_usage` MCP tool
  is read-only and scoped to the agent's project; the purge is a human action
  (the bridge token is refused on it). See
  `docs/operations/disk-usage.md`.

- « Revalider l'audit » now re-reads the docs whose sections are all
  unanchored (no citation, no repository path), once per change (KT-1292).
  Such a doc has no verdict, so nothing ever sent it to an agent. A review
  stamp in `citations.json` (the doc's hash and the digest of its owning
  step's sources) queues it for that step when it has none, or when the doc
  or the step's sources changed since. After the pass the doc is stamped
  whether or not the agent added an anchor: the plan is clean again and the
  pass does not come back. The panel shows « re-read on <date> » for each.
- A principal can approve a delivery with a DoD item carried to another ticket
  (KT-1291): `dod_verifications: [{dod_id, met: false, carried_to: "KT-1290",
  evidence}]`. The approve integrates through the normal path; the item is
  detached from the task and a `dod_carried` event names the carrier. The
  carrier must exist, be open, share a project with the task and state the
  requirement the worker was briefed with (the execution records its DoD
  sentences at launch, and a `task_update` records the items it removes), or
  the approve is refused with the cause; the detach runs in the review
  checkpoint's transaction. A manifest that references a DoD id removed or
  carried after the delivery is judged against the execution's launch-time DoD
  snapshot instead of being refused as an unknown id.
- A `#symbol` citation anchor that names a definition the cited range
  contains must open that range (KT-1074). When the range holds a definition
  of the symbol (`fn`, `def`, `class`, `struct`, `enum`, `const`,
  `interface`, `function`, `type`), that definition opens it: its doc comment
  and attributes, then up to 5 sibling lines (a constant and its comment),
  may sit above it. A range that holds no definition only has to name the
  symbol, so a call site or a usage still backs a claim about wiring. A
  range that starts a function early fails the docs check and the runtime
  verifier with `a definition anchor must start the range`, and
  `make docs-reanchor` proposes the range starting at the doc comment or
  attributes of the definition. 213 citations of `docs/` were repaired by
  reading their sentence (193 flagged by the rule, 15 moved by integrations, 5 more found by
  the review sample);
  the full check over every doc is green.
- Doc relevance in « Revalider l'audit » (KT-1168). Beyond citation
  fingerprints, Kronn judges every section of the AI docs against the code as
  it is now, deterministically and without a model: `anchors_intact`,
  `outdated` (a cited line changed or vanished), `no_longer_relevant` (every
  anchor is gone) or `unanchored` (no citation, no path: listed as
  unverifiable, never called true). A backticked path counts as gone only
  when it resolves nowhere (project root, the doc's folder, the tail of any
  project file; not generated or git-ignored), and examples, history lines,
  proposals, templates and changelog docs are not claims. The panel shows the
  counts, the evidence and a proposed correction per section, the agent pass
  receives them (unanchored sections as « check or anchor »), and the sources
  badge turns to « to re-verify » with the vanished paths counted apart from
  the broken citations. A vanished mention is accepted per section with
  « Vérifié, inchangé » only, and from the project card only the paths its
  stale block displayed; a validation or a revalidation carries that
  acceptance over and never adds to it. The history and example rules apply to
  the sentence holding the path, not to the whole line, and a proposal heading
  must be a proposal phrase (« Ideas », « Future work »), not a word inside a
  real title.
- A room can reach other projects, decided by a human (KT-1270). A room keeps
  its own project: its working directory, where what it creates lands, and the
  only project whose credentials and API configs it may use. In the room
  settings a human opens another project in read (its tasks, rooms, runs,
  pages) or in write (planning only: create and edit its tasks), or every
  project, the latter in write only after a typed confirmation sent with the
  scope the screen showed. An agent asks with the MCP tool `room_scope`; the
  request grants nothing until a human approves it, narrows it to read or
  declines it. The header always shows what the room reaches (a red badge for
  every project) and the requests waiting. Grants are re-read on every agent
  call, so a revoke applies at once; they never open another project's
  credentials, never run its workflows, never pass to worker rooms, and are
  kept as a journal with a `[kronn-scope: …]` line in the room for each change.
  Only the room's own agents reach its grants, never a workflow step or a
  worker posting in it. A workflow's runs reach other projects through a scope a
  human sets on the workflow page; any change to the workflow suspends it until
  validated again, and the launch card shows it. Design and refused attacks:
  `docs/design/discussion-project-scope.md`.

- AI documentation template and audit v2 gap audit (KT-819). The project
  facts of the briefing gain « Documentation externe » (Confluence, Notion,
  wiki, other; https addresses only, no credentials, at most 6), rendered in
  `docs/project.md`; a Confluence space is read back with one read-only GET
  through the project's Atlassian connection, and a space the connection
  cannot see is « not verified », never a mismatch. The audit seeds
  `gotchas/known-traps.md` (the briefing's traps), `people/team.md` (roles,
  names, contacts and the team answer) and `conventions/git-workflow.md`
  (the answered Git facts) from the briefing, and lists the existing
  documents moved to `legacy/` that read as conventions, gotchas or people in
  each folder's README; a generated file carries the briefing marker, is
  regenerated by each audit and is yours once you delete that line. The
  project index's Stack table has `In place`, `Latest stable` and `Support
  end` columns filled only from the dated version sources of the run
  (`not verified: no source` otherwise), and a non-blocking `stack_currency`
  diagnostic names the rows that lack their source and date. Kronn removes the
  legacy « FIRST-RUN TASK » bootstrap block from a docs entry file at boot,
  audited or not, without cutting what a person wrote around it.
- Tasks show which agents are on them (KT-154). A task carries one or more
  assignees, each an exact identity — the room's agent, a punctual agent or a
  joined CLI session — with an optional owner / reviewer role, so two Codex
  CLIs stay distinguishable. The discussion plan shows them as pastilles on
  each row (Focus and All views) and the worker of a running task execution
  appears there too, with its agent and tier, without any manual assignment.
  Assign and unassign from the plan row's quick menu or from the task sheet;
  agents use the new `task_assign` MCP tool (`unassign: true` removes), and moving a
  task to in_progress assigns the calling CLI as its owner when nobody is on
  it.
- The briefing records the team's rules and bans (KT-1193). « Règles et
  interdits » holds up to 20 one-line entries of at most 200 characters,
  each a rule (always) or a ban (never). A ban may carry a pattern Kronn
  checks: a dependency declared in `package.json`, `Cargo.toml` (dotted keys
  included), `composer.json`, `requirements*.txt` or `pyproject.toml` (PEP 621
  and Poetry), a module read from each language's import syntax (comments
  skipped; `lodash` matches `lodash/fp`, not `lodash.debounce`), or a file
  glob matched in linear time (at most three `*`/`**`). The entries go to a
  `## Project rules` section of `docs/briefing.md` with a machine copy, put
  back after the AI review, before `## Kronn options`; a briefing without
  rules keeps its earlier bytes. Kronn writes them into a generated section
  of the coding-rules doc (in the detected docs folder), restored after
  every audit step, so every agent of the project reads them; a section that
  lost its end marker is repaired without touching human lines. The briefing
  cannot be saved while an audit runs on the project (409). The steps
  that judge the code (tech debt and every `inconsistencies-*` step) get a
  short rules block: a violated ban or unmet rule is a TD with its
  `file:line` proof, and no fix may break a ban. Each ban with a pattern is
  checked by a read-only detector (`project-ban`, no symlink followed, size
  and count caps) whose hits join the step 8 signals and need a disposition.
- The briefing form reopens with the saved answers, and every project fact
  takes an optional note (KT-1196). `GET /api/projects/{id}/briefing/answers`
  reads `docs/briefing.md` (or the DB notes before the project has a docs
  folder) back into the six questions, the project facts, the existing
  documentation and the Kronn options; unknown or malformed parts are left
  empty, and a section the form does not know stays inside the last answer
  so a re-save keeps it. Saving again unchanged rewrites the same bytes.
  Each fact (tracker, project key, forge, repository, default branch, commit
  convention, sign-off, merge strategy, review, release cadence,
  environments, people) gets « Note (facultatif) », up to 300 characters,
  validated like the other briefing texts, stored in the machine copy (absent
  when empty, so older briefings stay byte-identical), rendered next to its
  fact in `## Project facts` and `docs/project.md`, given to the audit with
  the fact and put back after an AI review.
  An answer line that would read as structure (a heading, a machine copy, a
  Kronn option line) is saved behind a `\` and given back as typed, and the
  questions are read in order, so an answer can never split the briefing.
  A click that only focuses a field no longer skips the prefill; when the
  saved briefing arrives after a real change, the typed text is kept, only
  the empty sections are filled from it, and « Charger le briefing
  enregistré » loads the rest, so a save never wipes what was not changed.
  The rules and bans (KT-1193) reload into their editor too, following the
  same late-prefill rule, so re-saving a reopened briefing no longer drops
  them; `## Project rules` ends the last answer instead of being read into
  it, and the reopened form is refused (409) while an audit runs.
- The briefing asks about existing documentation and linked projects
  (KT-999). « Existing documentation? » lists the locations Kronn finds in
  the repository (`README*`, `docs/`, `doc/`, `wiki/`, ADR folders,
  `CONTRIBUTING*`, root `*.md`, OpenAPI/Swagger files, `apidoc/`; symlinks
  skipped), pre-checked, each with the date of its last commit (`git log -1`,
  literal pathspec, bounded and timed out). A path can be added by hand: it
  must be relative, without `..`, existing, and stay inside the repository
  once symlinks are resolved, otherwise the server answers 400. Each kept
  location gets a closed state (recent, old, obsolete, partial, reference), a
  one-line note and, at most one per kind, the source-of-truth mark (never on
  an obsolete location). The answers go to a `## Existing documentation`
  section of `docs/briefing.md` with a machine copy, put back as answered
  after the AI review, before `## Kronn options`. The section stays out of
  the audit and review prompts until a bench run shows a gain. The linked
  repos drawer is available in the briefing with one-click suggestions whose
  description is left to fill in.
- Linked repos show which AI doc they really have (KT-999): « doc IA »
  (`docs/AGENTS.md` or the legacy `ai/index.md`), « README seulement » or
  « pas de doc IA ».
- The audit can read the briefing's existing documentation (KT-999), off by
  default: `audit_briefing_docs = true` in `config.toml`, or
  `KRONN_EXP_BRIEFING_DOCS=1` for one run (bench). It reaches only the steps
  that use it — step 1 (`AGENTS.md`), step 8 (tech debt) and the API design
  audit — as a `## Existing documentation` block within the 6,000-character
  briefing cap (the block takes at most 3,000 of them, the briefing text gives
  way). A `reference` location is read, reused and cited; an `obsolete` one IS
  tech debt: step 8 records a TD row and a detail file citing the location and
  its last-commit date whatever the audit's scope, and no step copies a fact
  from it nor calls it out of scope — and on a later audit step 8 never parks
  it under `## Reconciliation candidates` as « out of scope this audit »;
  `old`/`partial` (and a state not provided) are verified before reuse, a
  `recent` one is spot-checked. The recorded locations are re-validated before
  they reach a prompt (a hand-edited `briefing.md` cannot add lines to it).
  Disabled, the prompts (and the audit golden) are unchanged.

- Docs record what a human validated and ask for a reconfirmation when Kronn
  changes it, never blocking (KT-1079). A validation (`validate-audit`, a TD
  decision on a validation card, « Vérifié, inchangé », a revalidation that
  leaves the docs as validated) records the digest of each validated doc in
  `docs/provenance.json`, next to `citations.json`; the validated text stays
  local under `.kronn/audit/validated-originals/` for the diff. No marker is
  added to the prose. Every automatic writer now applies its change to a
  human-validated doc or an `owner="human"` section instead of refusing,
  reverting or skipping it: full and partial audits (no more restore + dated
  `human-section-diff` report), revalidation (re-anchoring and the agent pass
  now cover human sections), template reinstall (human sections are no longer
  put back), documentary repair (citation bundles in human sections are
  normalized) and the tech-debt index resync. The doc is then marked « to
  reconfirm » with the writer and a before/after diff. A « À reconfirmer (N) »
  button next to the audit status on the project card opens the docs to
  reconfirm with their diff; « Confirmer » (one doc or all,
  `POST /api/projects/:id/doc-provenance/confirm`, human only) restores the
  provenance. The template reinstall plan names the human-validated content
  it will rewrite before writing. The learning gate weights its sources by
  this provenance: a source in a doc a human validated and nothing changed
  counts as proof; one only an AI wrote, or changed since its validation,
  does not count as verified evidence for a `fact` until reconfirmed. The
  audit prompts let an audit correct a human section where the code
  contradicts it. No database change.
- Settle the audit's tech debt from the project card (KT-982). A "Dette
  technique" block shows the progress (confirmed, kept on purpose, deferred,
  rejected, remaining), the remaining count per severity and the Validated
  rule, and opens a review screen built for a hundred items and more: filters
  (to settle, deferred included; settled; all), severity chips, a search over
  id, title and cited files, a list grouped by severity with a checkbox on
  every row, and a bulk bar that settles the whole selection in one atomic
  request (one unknown id writes nothing). The detail shows the problem, the
  clickable `file:line` pointers (opened in the code viewer), the verification
  criterion and four decisions; keeping an item on purpose records the reason
  in the project's decision log, trusted as a human decision like a validation
  card. Each item links its Kronn task or creates one (the KT-1167 conversion
  for one sheet); GitHub and Jira are disabled placeholders. "Discuter de cette
  TD" shows the validation messages that name the item and the item's own
  discussion, created with the agents picked in the comparative Quick Prompt
  selector (now a shared component). New endpoints:
  `GET /api/projects/{id}/audit/tds`, `POST …/audit/tds/decide`,
  `POST …/audit/tds/{td_id}/task`, `GET|POST …/audit/tds/{td_id}/talk`.
- A project can be validated without the validation discussion's terminal
  marker once every Critical and High tech-debt sheet is confirmed, rejected
  or kept on purpose (a deferral does not count) and the other gates pass
  (KT-982). The discussion path still works.
- CI checks the `[src: file:]` citations of Kronn's own docs (KT-1059). A
  test (`cargo test docs_citations`, run by the backend suite) walks
  `docs/**/*.md` (except the historical `docs/releases/`) and `backend/docs/`
  with the citation verifier's own extraction, path jail and line counting,
  and fails on a citation that has no line, names a missing file or a
  directory, is absolute or leaves the repo root, or points past the end of
  the file. Only a binary file may be cited without a line. Fenced code and
  placeholder examples (`<path>:<line>`) are skipped; a citation in backticks
  is checked. The report lists `doc:line`, the citation and the reason. The
  corpus was repaired to zero failures: every line-less citation now points
  at the symbol it means, paths of moved or split files were re-located, and
  sentences about code that no longer exists were rewritten.
- The audit's tech-debt baseline runs as code (KT-152). Fourteen new
  deterministic detectors cover what the step 8 prompt used to ask the agent
  to grep: Dockerfile (root user, no `HEALTHCHECK`, apt flags and list
  cleanup, base image untagged or `latest`, `ADD` of a remote URL,
  secret-named `ARG`), compose images untagged or `latest`, CI
  (`StrictHostKeyChecking=no`, `pull_request_target`, actions on a moving
  ref), tracked `.env` files (filenames only), templates (`<img>` without
  `alt`, inline `<script>`/`<style>` over 50 lines) and OSS community files,
  gated on OSS intent. Each finding carries `file:line` evidence, and step 8
  is re-run when one is left unaddressed, like the existing detectors. PHP,
  Node.js, Rust, Python and Go items (`display_errors`, `opcache`,
  lockfile installs, `cargo deny`, `govulncheck`…) reach the prompt only
  when that stack is detected. The step 8 prompt shrinks from 19,941 to
  about 10,600 bytes (about 5,400 to 2,900 tokens); every former baseline
  item is mapped to a detector, a stack item or a remaining prompt line by a
  test.

- Cross-agent audit, experimental and off by default (KT-1183). A briefing
  option (« Audit croisé ») picks 2 or 3 agents and shows the estimated cost
  first: ×N to ×2N the security step's last measured single-agent cost (from
  the same step figures as `GET /api/audit-runs/{id}/cost`), or the measured
  cost of an earlier cross-agent run with as many agents. On the security
  step only, the agents detect in parallel without seeing each other's work
  and return their findings as a machine-readable block; Kronn checks every
  citation (file, lines, quoted text) and drops a false one with no model
  call, groups findings about the same place (information only), then has
  each finding challenged by another agent, who is not told who found it.
  Never a vote: a refutation dismisses a finding only with a proof that passes
  the same citation check, otherwise the finding stays, marked contested; the
  author may answer a proven refutation once, and two proven positions become
  a human `kronn-question` card with both arguments side by side. At most 30
  findings are challenged per step, the rest listed as not cross-checked.
  Kronn writes the index and the TD sheets (an existing sheet keeps its body,
  only a Kronn provenance block changes); every finding's provenance —
  found by, detection count, checked by, proof, reply, verdict — and the
  dropped and dismissed findings are stored (migration 257). Without the
  option the step is unchanged, byte for byte.
- Citations can name what they point at (KT-1074):
  `[src: file: path:120-140#symbol]`. The symbol must appear as a whole word
  inside the cited lines; the check is textual, no code is parsed. The docs CI
  check fails on an anchor that left its range and, when the symbol still
  exists in the file, suggests the new range (`try path:436-465#symbol`). The
  runtime verifier applies the same rule to agent and audit citations: a
  missed anchor reads as `out_of_bounds` with a detail saying where the
  symbol appears, so the existing red/amber badges are unchanged. Re-anchoring
  a moved citation keeps its anchor. Documented in the citation spec and in
  `docs/AGENTS.md`. Every one of the 1,345 file citations of
  `docs/architecture/` and `docs/operations/` was re-read against the code
  and now carries an anchor: about 60 % pointed beside what they described
  (a random sample of 24 measured 10 beside before, 0 after); they were
  re-pointed, and some 50 sentences describing code that has since changed
  were rewritten (ADR-002 no longer claims an advisory integration lease,
  the HTTP workspace catalogue lists its 13 tools including `git_commit`,
  audits accept every agent but Vibe, among others).
- The docs citation check is scoped per pull request (KT-1195). A pull
  request that changes no file under `docs/` or `backend/docs/` gets the
  check as a counted warning (job summary and one annotation per failure),
  never a failure; one that changes docs fails only on citations of the docs
  it changes, with the repair command in the message
  (`make docs-reanchor DOCS="…"`). A pull request that changes `VERSION` (a
  release), a release run without a pull-request proof, a manual run and a
  local `cargo test` stay strict on every doc. CI lists the changed paths
  from the pull request's merge commit (`scripts/ci/docs_citations_changed.sh`,
  failing rather than guessing when it cannot) and passes them to the test.
  `make docs-reanchor` re-points a citation whose `#symbol` left its range
  only when the symbol occurs once in the cited file, keeping the span length
  (clamped) and the anchor; with several occurrences it rewrites nothing and
  lists the candidate lines, and the CI message no longer suggests a « try »
  range for such an anchor (the nearest occurrence was often the wrong one). Missing files, missing lines and lines past the
  end are never rewritten, and only the checked docs are written. It is the
  first step of the release checklist (`docs/operations/releasing.md`). The
  CI check, the `verified` badge and the runtime verifier keep one anchor
  rule with no tolerance window, now stated in the citation spec. The 16
  citations the integration had shifted were repaired: 1 by the command, 15
  by hand, because their symbol occurs several times (for one of them the
  nearest occurrence was the wrong one).
- The AI doc folder is configurable (KT-263). Settings › Project scanning sets
  a default folder relative to every project root, and each project can
  override it from its page; without an override, detection stays `docs/` →
  `doc/` → `ai/`. Detection, the audit badge, the evidence file, the context audit, the
  redirectors and the doc viewer all read the same resolver. A folder that is
  absolute, uses `..`, points into `.git` or reaches outside the project
  through a symlink is refused with a 400 when saved and fails closed when
  used. New column `projects.docs_dir` (migration 258).
- The audit pipeline writes under the project's AI doc folder (KT-1192). One
  root, resolved per run: step targets, TD sheets, `decisions.md`,
  `briefing.md`, `project.md`, the `inconsistencies-*.md` indexes, checksums,
  the installed templates, the root redirectors and every path the step,
  validation and briefing prompts quote follow it, for Full and partial
  audits, sub-audits, revalidation, template reinstall and merge. A project
  without an override keeps `docs/` with byte-identical prompts. The legacy
  `ai/` → `docs/` migration still applies only to unconfigured projects.
- The briefing asks for the « Dossier de la documentation IA » (KT-1192),
  pre-filled with the project's override, else the folder it already
  documents itself in, else the default from Settings, else `docs/`. Saving
  it sets the project's folder through the same containment check (400 on
  refusal); the default from Settings now only pre-fills this field and
  never applies to a project by itself.
- No audit starts without a briefing (KT-1192): Full, partial, sub-audit and
  revalidation launches answer `error_code: briefing_required`. In the UI
  the briefing step is required and its form gets a one-click « Utiliser
  les valeurs par défaut ». An MCP launch (`audit_launch`, `audit_prepare`)
  receives the briefing as ready-to-post `kronn-question` cards in the
  discussion's language, as a set of its own (`briefing-<field>.<set>`
  keys); once every card of the latest set is answered in that discussion,
  Kronn saves the briefing through the form's validation, and answers to a
  superseded set are saved nowhere. A launch with no
  discussion (a workflow run) gets default answers, flagged « valeurs par
  défaut — à compléter » on the project card.
- An existing AI doc folder is handled explicitly (KT-1192). Kronn's own docs
  are updated in place without a question; they need both a `.kronn.json`
  recording an audit or a bootstrap and a structural marker (a line-leading
  `kronn:doc-version` / `kronn:generated` header, paired `kronn:section` or
  managed-block markers outside code, or the AGENTS.md signature). A mention
  of Kronn, or markers without that record, is a human's docs.
  A folder of human docs shows « Le dossier … utilisé par Kronn existe déjà »
  and asks: put Kronn's docs in another folder, or move them to
  `<folder>/legacy/` (file count, broken links and a detected mkdocs,
  Docusaurus or VitePress config are stated first); the audit then reads
  them as its primary source. The silent pre-audit move is gone: nothing
  moves without that choice, never into an existing `legacy/`, and only once
  the briefing is committed (a failed save leaves the folder as it was). An
  audit or unattended launch on such a folder without a choice stops with
  `docs_folder_choice_required`.
- Audit versions and standards are judged from dated sources (KT-1000). The
  tech-debt step and the security pass get a « Technologies and standards »
  block: the project's stored dependency watch (version in place, latest,
  gap, check date; never refreshed by the audit) and the runtime versions the
  repository pins (`node-version`/`python-version`/… in `.github/workflows`
  and `.gitlab-ci.yml`, `.nvmrc`, `.node-version`, `.python-version`,
  `.ruby-version`, `rust-toolchain(.toml)`, `.tool-versions`), each with its
  citation. Kronn picks the live source from configuration — context7 when the
  agent mounts it from the project's MCP config, else a web tool when the
  agent has one (a direct Claude CLI with full access), else the dependency
  watch — and the prompt states it. New citation kind
  `[src: deps: <manager>/<package>@<YYYY-MM-DD>]`: in an audit it is checked
  against the injected watch (absent row or other date = fabricated);
  elsewhere it stays unchecked. The `Dependencies` coverage row must state
  `source used: …` or `not verified: no source`, otherwise step 8 is re-run.
- « En faire des tâches Kronn » on the card of a project with tech debt
  (KT-1167). `POST /api/projects/:id/audit/td-tasks` turns every
  `docs/tech-debt/TD-*.md` sheet into a planning task linked to the project:
  one parent task per theme (the sheet's `Category`, else its `Area`) with one
  subtask per sheet. A subtask carries the sheet's title, its severity as
  priority (Critical/High/Low as is, Medium and unknown as normal), theme,
  area, status, problem excerpt, sheet path and a verification criterion as
  its Definition of Done. Every task is keyed (`audit:<project>:<TD-id>`, and
  `audit:<project>:theme:<theme>` for a parent) on the existing planning
  idempotency key, so a second click creates nothing and reports the tasks
  already linked; rejected sheets and sheets covered by an accepted decision
  are skipped. The answer counts created, already linked and skipped. The
  sheets stay in the repository as the source of truth. The briefing form
  gains « Faire des TD trouvées des tâches Kronn »: saved in `docs/briefing.md`
  (and the DB notes) as a `## Kronn options` line, it runs the same
  conversion at the end of a successful Full audit (`td_tasks` SSE event, a
  warning on failure).
- The audit recap now shows what a run consumed per phase and in total
  (KT-1084): the briefing, the audit steps and the validation discussion, each
  with its tokens (uncached input, output, cache) and cost, plus a grand total.
  `GET /api/audit-runs/:run_id/cost` adds them up from what each phase already
  stored, nothing is re-measured. A step a resume carried over is counted once,
  at the run that really ran it. A phase no agent reported shows « non mesuré »,
  never 0; a sum over an unknown cost is marked `≥`, an estimated one `≈`. The
  briefing is linked to the project, not to a run: it is the briefing discussion
  opened since the previous Full audit.
- « Revalider l'audit » on the card of an audited or validated project
  (KT-1168). Kronn first checks every citation against the fingerprints
  recorded at validation, without a model:
  `GET /api/projects/:id/revalidation` lists, per doc, the moved citations,
  the broken ones (changed, removed, file missing), the ones inside
  `owner="human"` sections, and the audit step that owns each doc.
  `POST /api/projects/:id/revalidate-audit` then rewrites the line number of
  each moved citation whose content is unchanged, also without a model. If
  any doc still has broken citations, it runs the step that owns that doc,
  and only that step, through the partial-audit pipeline (same lease, run
  row, Stop and gates). That step gets the re-verification prompt, Kronn's
  findings for its doc and the new builtin skill `audit-revalidation`. A
  citation that no longer resolves is named before the agent runs, and the
  step cannot end `unchanged` while it is still there. Human sections are
  listed for their owner and never edited. Docs the agent rewrote get fresh
  fingerprints, so the « Sources » badge is clean again once nothing is left.
  A run that rewrote nothing, because the agent ended `unchanged` or was
  stopped first, restores the validation it revoked. Relaunching the action
  resumes: it recomputes what is left. Each section of the panel has
  « Vérifié, inchangé », wired to the existing acknowledgement, and every
  error, stop or interruption offers « Reprendre ». The routes are not on the
  bridge list.
- The briefing now asks about the project itself, on closed cards, and the
  audit writes `docs/project.md` from the answers (KT-1173). Next to the six
  questions, the form shows one choice card per fact: tracker, project key,
  forge, repository, default branch, commit convention, sign-off, merge
  strategy, review, release cadence, environments (with optional URLs) and
  roles (arbiter, tech lead, DevOps, PO, QA, with only the name and contact
  the user types). The backend refuses any option outside a card. Answers go
  to a `## Project facts` section of `docs/briefing.md`, kept as answered
  even when the AI review rewrites the file. Each Full audit renders
  `docs/project.md` deterministically (template v2 ships its skeleton and
  routes it from `docs/AGENTS.md`). An open repository is taken from the git
  remote. When the project has a Jira (`mcp-atlassian`) or GitHub
  (`mcp-github`) API connection, the Jira project key, the repository and
  its default branch are checked read-only with one GET each, and a
  mismatch is flagged on the page. With no connection, the value is marked
  "not verified". A `docs/project.md` without its generated marker is never
  rewritten.

- « Recréer les fichiers du template » (KT-1166): a damaged or missing AI-doc
  template file can be reinstalled on purpose. The project card lists the files
  that would be replaced and asks for confirmation; every replaced file is first
  copied to `.kronn/audit/template-backups/<timestamp>/`, then the result names
  what was replaced and where the originals are. Only the template's own files
  are written and `owner="human"` sections are kept as they are. Files still
  template-like or broken (empty, unfilled `{{placeholders}}`, unpaired section
  markers) are repaired by default; files holding real audit content are listed
  apart with a warning, unchecked, and replaced only when ticked (the POST takes
  the explicit path list and refuses any path the plan does not offer). The action is
  refused while an audit holds the project's lease. It is a human action: the
  two routes (`GET .../template-reinstall/plan`, `POST .../template-reinstall`)
  are off the bridge-token list and refuse a bridge caller. A failure keeps
  « Reprendre ».
- After an audit with failed steps, a single step can be retried alone, or a
  human can accept it as it stands (KT-1131). On each failed line of the
  timeline, « Relancer cette étape » resumes the interrupted run with
  `retry_steps` (the other failed steps stay failed and unrun), and
  « Accepter en l'état » calls
  `POST /api/projects/:id/audit-runs/:run_id/steps/:step/accept`, which
  records who, when and an optional reason (migration 246). The route is off
  the bridge list and refuses any agent token. It is refused when the target
  is missing, empty or still a template. Once no failed step is left,
  « Finaliser l'audit » completes the run without an agent step, so the
  project can be validated without a global audit. The recap and the
  validation discussion list accepted steps apart from succeeded and
  unchanged ones.

- A repository that already has its own docs can now receive the AI-doc
  template v2 by merge (KT-1172). `GET /api/projects/:id/template-merge/plan`
  shows, without writing anything, a correspondence table that maps each
  existing doc, section by section, to a v2 file, plus one diff per file.
  `POST /api/projects/:id/template-merge` (`{"base": "local"}` or
  `"remote"` for the fetched default branch) applies it in a
  `kronn/template-v2-merge` worktree: mapped sections are copied verbatim into
  the v2 files that do not exist yet, existing files are never rewritten,
  moved or quarantined, and the merge stops if any existing doc changes.
  Unmapped sections are listed and stay where they are. Calling it again
  resumes the same worktree. `.../accept` commits the files you keep on that
  branch, and `DELETE` drops the worktree.
- The project's Audit tab now offers this merge (KT-1181). On a documented
  repository that was never merged, "Lancer l'audit" first opens the merge
  panel, which also lets you run the audit without merging. The panel shows
  the correspondence table with a badge per action, with unmapped sections
  highlighted as left in place, and warns about docs that git does not track
  yet so you commit them first. You pick the local or remote base, then get
  one diff per file that you accept one by one or all at once. You can also
  resume or abandon the merge. Every error says what to fix and offers
  "Reprendre".
- The template merge plan now says where a merge stands (KT-1182). Its new
  `merge` field reports whether the `kronn/template-v2-merge` worktree and
  branch exist, how many files wait for review, and how many commits the
  branch has that the checkout's branch (and the remote default branch)
  does not. It reads git only: no fetch, no write. The panel opens on "Fusion
  en cours" with Reprendre and Abandonner when a worktree exists, and on
  "Fusion acceptée, à intégrer (N commits)" when accepted commits wait to be
  merged. "Lancer l'audit" no longer offers the merge again in that case.
- Audit drift is now measured per citation (KT-1175, KT-1078). Validation,
  and every audit baseline, records a fingerprint of each `[src: path:line]`
  the docs cite in `docs/citations.json`: path, line and a SHA-256 of the
  cited line (whitespace-normalised, secrets redacted), never its content.
  A section with fingerprints goes stale only when one of its cited lines
  changes or disappears, and the drift report names the citation; a commit
  elsewhere no longer marks it stale. A line that only moved, by up to 50
  lines, is re-anchored. `GET /api/projects/:id/citation-drift` lists broken
  and moved citations per section, and
  `POST /api/projects/:id/citation-drift/acknowledge` (`{"doc": "docs/…"}`)
  records a section as checked and unchanged without rewriting it. A new
  "Sources clean" badge, kept apart from the dated "Validated" one, turns to
  "To re-verify" as soon as a citation breaks, with no new audit. An
  optional cron, `audit_reverify_cron` in `config.toml` (off by default),
  re-verifies every project on that schedule.
- A decision is no longer reported as tech debt (KT-1174). The v2 template's
  `docs/decisions.md` is now a list of ADRs, with no cap on their number:
  `## ADR-NNNN: title`, then status (`proposed`, `accepted`, `superseded by
  ADR-NNNN`), date, optional `Supersedes`, `Covers`, `Source` and `Links`,
  and Context, Decision and Consequences sections. After the audit steps,
  Kronn settles every TD sheet an accepted ADR covers, either because the ADR
  lists its slug in `Covers` (a re-discovery on another day still matches)
  or because the sheet names the ADR in `Decision record`: its status becomes
  `Accepted decision`, the sheet points at the ADR, and the TD leaves the
  index and the validation. `proposed` and superseded ADRs cover nothing.
  Only an ADR already accepted when the run started (in that version: a
  `Covers` entry added during the run does not count), or accepted by a human
  in a validation card, may settle a TD. An ADR the run itself wrote or
  flipped to `accepted` goes back to `proposed`, and the finding stays in the
  validation, where accepting it as a decision records a covering ADR. A
  sheet's `Decision record` line counts only if that line, naming that ADR,
  was on the sheet when the run started: one the run writes on a new sheet,
  adds to an existing one or changes leaves the finding in the validation.
  An ADR a human accepts during a run, or while it is paused before a
  resume, counts from the next fresh run. A resumed run keeps the baseline
  of the run it continues (migration 252, `audit_runs.adr_baseline` and
  `audit_runs.adr_record_baseline`). The steps that write TDs receive only the ADRs
  in force before the run. Existing tables keep working: a row that links a
  TD sheet still covers it.
- Each agent reply says whether it continued the agent's conversation
  (KT-1202). Every turn relaunches the CLI or ACP process; memory survives
  through `--resume` (Claude Code) or ACP `session/resume`, with the full
  history sent when that is not possible. The reply now records which
  happened: « reprise », « nouvelle session » or « historique complet »
  (resume refused by the runtime), shown discreetly after the model, with the
  reason in the tooltip (no stored conversation, working directory changed,
  previous turn unfinished, conversation expired, history changed, nothing
  new, resume not supported…). Agents that never resume record nothing.
  Migration 259 adds `messages.session_continuity`.

### Changed

- The dark theme is now VS Code's Dark Modern palette: flat greys,
  `#0078d4` filled controls and `#4daafc` accents, no glow, scanlines or
  aurora. It applies to everyone on Dark or on System with a dark OS, with
  no setting to change. It is held to the dark-theme contrast floors (4.5:1
  for every text role) and covered by the dark-theme axe pass. The former
  purple palette, with its neon effects, becomes the secret Arcade theme,
  unlocked with a code in Settings like the other secret themes.
- CI no longer reruns the whole suite on a tree a pull request already
  tested green (KT-1170, reshaped by main's shared-cache change): a push to
  `main` only builds and saves the caches pull requests read, and a release
  tag reruns no test workflow.


- The macOS and Windows portability CI job compiles only the four suites it
  runs. A new default Cargo feature, `full-tests`, gates every other
  unit-test module; the job builds with `--no-default-features`. The full
  lib-test binary (9 200 tests) made rustc peak above 13 GB while the
  standard macOS runner has 7 GB, so the first step swapped for 45 to 55
  minutes. The job also prints its rustc and machine facts, since two runner
  images with different Rust releases rotate and invalidate the shared
  cache. Every other job keeps the default feature set. The `with_conn`
  unwrap lint exempts exactly the item carrying a `#[cfg(test)]` or
  `#[cfg(all(test, …))]` attribute instead of cutting each file at its
  first `#[cfg(test)]`, so production code after a `mod tests;` line is
  scanned again. (KT-15)
- Every view has its own address, and the browser's Back, Forward and reload
  follow it: the pages (`/projects`, `/discussions`, `/planning`,
  `/workflows`, `/pages`, `/plugins`, `/config`), what is open in them
  (`/projects/<id>`, `/discussions/<id>?message=<id>`, `/planning/<id>`,
  `/workflows/<id>/runs/<id>`, `/workflows/qp/<id>` and the other Automation
  tabs, `/pages/<id>`, `/plugins/<id>`) and the whole-window views
  (`/standalone/pages/<id>`, with its view parameters such as `?tv=1`, the
  Page and discussion mosaics). An address can be copied, sent and reopened.
  The links written before — `#discussion-<id>`, `#project-<id>`,
  `#page/<id>?…`, the mosaics' `#…/mosaic?…`, `#settings/artifacts?…` and
  `#config` —
  keep working for good, from Live Pages, messages, bookmarks and the `kronn`
  CLI alike. The desktop app's embedded server answers a reload on any of these
  addresses with the app.
- Ctrl/Cmd-click, Shift-click or a middle click opens a page in a new tab:
  the navigation tabs, an Artifact's related discussion, workflows and
  publication runs, and the rows and links that open a discussion, a
  project and its views, a workflow and its runs, a Quick Prompt/API/Exec, a
  task, a plugin or an Artifact, the "Fix" of the pre-launch agent check and
  a skill's "Open in Config". A plain click still opens it in place; the
  look of every element is unchanged.
- Views inside a page have their own address too: a project's view
  (`/projects/<id>/code?file=<path>&line=<n>`, `/projects/<id>/git`, …), the
  workflow wizard (`/workflows/new`, `/workflows/<id>/edit`), a comparison of
  discussions (`/discussions/compare/<run>`) and a Configuration section
  (`/config#<section>`). A file an agent cites, a just-validated audit or a
  just-improved Quick Prompt now opens at such an address, so Back, a reload
  and a shared link land on the same view. A code view opened without a file
  names the file it shows in its address, so Back to it reopens that file;
  choosing a type's list from one of its Automation resources is a step Back
  undoes; a reload, Back or Forward on a Configuration section brings it
  into view again.
- "Open the retention setting" on the run-retention banner (Automation and
  Configuration) lands on the setting, centred and focused, at its address
  `/config#run-payload-retention`; it used to stop short of it while the page
  was still loading. Every Configuration anchor (`/config#<section>`) waits
  for the page to settle the same way, and the sections in Configuration's
  side menu are links: Ctrl/Cmd-click opens one in a new tab.
- A release tag only checks the version, builds and verifies the installers
  and creates the draft; it no longer reruns the CI suites the merged pull
  request already passed.

### Fixed

- A skill imported with a workflow or a Page never pre-approves tools: its
  `allowed-tools` is dropped on import, whoever imports it, and an import
  whose skill still declares tools after that is refused. A human sets it
  again in the skill editor if wanted.
- A worker's HTTP agent can no longer run a tool its catalogue leaves out
  (backlog tools, `qp_update`, `skill_update`…) by naming it: the call is
  refused before any access check.
- Deleting a workflow is no longer blocked by interrupted runs whose
  worktree was removed since: only a worktree still on disk, which could be
  resumed or discarded, holds the deletion.
- Integration validations that need Docker work again, on a human's
  decision (KT-1433). Since 0.15.1 they run in the lane sandbox, which keeps
  Docker closed, so `make iso-deps` and the like always failed and the
  delivery was sent back to the worker as if its code were wrong. A
  validation that fails because of its environment now holds the execution
  with the fix instead, and when the sandbox closed Docker, Kronn asks on a
  card: refuse, allow for this discussion, or allow for the project; an
  answer that allows resumes the integration on its own. Project settings
  gain « Accès des validations d'intégration »: sandbox only, Docker allowed
  (the Docker socket alone is opened), or full access behind a warning.
- DOMPurify 3.4.17 (GHSA-cg2g-xh64-v4q7, critical: a split template bypassed
  `SAFE_FOR_TEMPLATES`; GHSA-mrv2-3pqr-j5jr, high: an `IN_PLACE` hook removing
  a parent).
- A release body stays under GitHub's 125,000-character limit: a CHANGELOG
  section too long for it is cut between two entries and links the full
  notes.
- An HTTP agent (Ollama, OpenRouter, LiteLLM…) can now use an image attached to
  the discussion as the source of an image generation (KT-1418). It was told
  only the filename while `media_generate` needs the context file id, so every
  call was refused and the refusal, reported as a success, was retried until the
  tool budget ran out. The image notice now gives each id, `media_generate`
  also accepts an exact filename of the same discussion, and the refusal lists
  the usable images. Agents are also told that a video model starts from a
  picture when its `frame_positions` lists `first_frame` or `last_frame`, and
  that an unknown reference count is not a refusal.
- A project granted to a discussion is now readable by its native Claude Code
  agent (KT-1406). From the next turn the project's checkout is a read-only
  directory of the agent (`--add-dir`; a write grant stays read-only on disk,
  revoking, the last grant included, removes it at the next turn). The agent's
  context, for Claude Code and Codex, names the granted projects with their path
  and access, plus the projects it can ask for, with `room_scope` as the way to
  ask the human. A refused read is explained by that context, not by `/add-dir`.
  An agent in full access is unchanged. A checkout reached through a symbolic
  link is never opened and a checkout swapped between the plan and the launch is
  refused. Codex keeps its default sandbox (reads the disk as it is, writes only
  in its working directory): no read isolation is claimed for it, that is
  KT-1409.
- TLS certificate checks use rustls-webpki 0.103.16 and webpki-roots /
  webpki-root-certs 1.0.10 (RUSTSEC-2026-0381, -0382, -0383: distrusted
  intermediates and emSign roots were accepted). The frontend tooling moves to
  vite 8.3.4 (launch-editor advisories in vite 8.2.0-8.2.3, dev server only)
  and vitest 4.1.12 (GHSA-qxg6-89mg-rqv8, GHSA-xp26-3454-mjgp in its mocker,
  test runner only).

- An Exec line awaiting a human's approval is visible before the run and can
  be approved where it is used (KT-1398). Since KT-1017 a run value handed to
  a program Kronn does not check alone (e.g. `aws`) is refused until a human
  approves the line, but a saved Quick Exec used as a CollectApiData source was
  invisible to the workflow's readiness, and its refusal said « approve this
  step in the editor » without naming it. Now:
  - the workflow page lists each such Quick Exec in its approval panel, with
    a one-click approval (stored on the Quick Exec, the explicit confirmation
    in the button);
  - the readiness and the workflow list count it as a human approval;
  - the Quick Exec library shows a « To approve » badge, and the Quick Exec
    and Exec forms say what is asked and why above the checkbox;
  - the run's refusal names the Quick Exec and where to approve it.
  An agent still cannot approve: the server refuses it.

- LiteLLM image generation works (KT-1214). Kronn sent images to
  `/v1/images`, a route a LiteLLM proxy does not have, and reported the 404 as
  "image response carries no b64_json payload"; it now uses
  `/v1/images/generations` with the OpenAI body (checked with
  `vertex_ai/gemini-3-pro-image`). A provider `usage` that states tokens but
  no price no longer records a 0 $ cost. A failed generation's bubble names
  the model that ran and offers « Relancer… », which opens the Assets
  Creator with the same prompt and kind. Opening a media room from the New
  discussion entry no longer stores an empty first message.
- A comment on an arbitration card whose workflow step has ended is answered
  by the room's own agent instead of nobody. It used to go to the step's
  closed CLI session and wake no one ("personne ne lira cette réponse
  maintenant"), so questions asked back on a framing room's cards got no
  reply. The agent's reply answers the comment, and the question stays
  pending for the next run; an answer or a refusal still waits for that run
  and wakes nobody, and a room without a native agent keeps the old notice.

- Confluence links read from Jira no longer come back as `***spaces/…`
  (KT-1331, the rest of KT-1249). The Atlassian plugin declares
  `CONFLUENCE_URL` as public configuration, like `JIRA_URL`: it was masked as a
  secret in every API response, so a workflow that rewrote a Jira description
  wrote the masked links back. The registry definition updates installed
  plugins at the next start.

- An audit no longer lets a fabricated `deps:` or `url:` provenance reach the
  Stack table in the default (warn) mode (KT-1308). A `[src: deps: …]`
  citation the injected dependency watch does not hold, and a
  `[src: url: …]` citation of a page the run's agent never fetched, are now
  blocking in the always-on documentary check, whatever the anti-hallucination
  mode. The step is retried with feedback telling the model to write
  `not verified: no source` instead of a « latest stable » it did not read; held
  and fetched citations, and `not verified`, pass. A page counts as fetched when
  the agent sent it to a fetch tool, opened it in a browser (`browser_navigate`,
  `navigate_page`), downloaded it with `curl` / `wget`, or was handed it by a
  documentation tool (the URLs a Context7 result names, so the citation the
  Context7 prompt asks for is not refused); a leading `www.` is ignored. This
  holds on the default ACP route too: the tool trace of a call now carries the
  URLs it opened, read from the whole input before the 2000-character excerpt
  cuts it (a long `WebFetch` prompt, a `curl` at the end of a long command), and
  the URLs a Context7 result named (Claude and Codex adapters; a native ACP
  agent when its update names the tool and carries its output). A native ACP
  tool is a fetch by its `kind: "fetch"` too, even when the runtime retitles it
  to its URL. A run whose agent reports no tool inputs (an HTTP model), that used
  a sub-agent, or that called a documentation tool whose result Kronn never saw
  leaves its `url:` citations unchecked, never blocked. A result that was seen
  and named no page (Context7's `resolve-library-id`, which comes before every
  `query-docs`) is not a blind spot: the trace carries a `result_seen` flag, so
  the normal resolve → query flow keeps `url:` citations checked on the ACP
  route as on the direct one. A trace keeps up to 256 URLs per call within
  24 KiB; a URL over 1000 characters is dropped, never cut. The URLs kept are
  put in one canonical, safe form, the same on the stored and the cited side:
  no scheme, no userinfo (`user:pass@`), the value of a credential-style query
  parameter masked as `name=***` (the name is judged after percent-decoding, so
  `%73ig`, `access%5Ftoken`, `api%2Dkey` or a doubly-encoded name are masked
  too; non-secret parameters such as `page=2` are kept), vendor token shapes
  redacted. A signed page still matches its citation, a page with another
  `page=` does not, and no secret is stored. The shell-download rule is
  conservative: only a real `curl` / `wget` invocation that is fully understood
  counts as a download, and only an `http(s)` address that is a destination of it
  (a positional argument, or `curl --url`), through `sudo` / `env` / `xargs`, a
  `;` `&&` `|` chain, or `sh -c`. A URL that is printed (`echo curl <url>`),
  quoted, in a `#` comment or a heredoc body, or the value of an option
  (`curl -d` / `--proxy` / `--referer` / `-o <url>`, `wget -O <url>`) is text,
  not a fetch; a command with a heredoc, a quote left open, or an option the
  reader does not know credits nothing, and so does a display-and-exit mode
  (`curl --version` / `-V` / `--help` / `-h` / `--manual` / `-M`, `wget
  --version` / `-V` / `--help` / `-h`), which transfers nothing. The options of
  a wrapper (`sudo`, `env`, `xargs`, `timeout`, `nice`, `nohup`, `time`,
  `command`, `exec`, `stdbuf`) are read with their values (`xargs -I curl
  printf …`: `curl` is a replacement token, `printf` the command); a wrapper
  option the reader does not know credits nothing. A citation of an uncredited
  URL is blocked with the "not verified" feedback. Only the full audit
  pipeline checks the run's provenance: a partial audit does not.
  The pre-step baseline and the blame ledger of the documentary gate (KT-1306)
  use the same run-aware analysis: an unbacked citation already there is in the
  baseline and does not block the step, one the step wrote blocks it and is
  charged to it. The parenthesised-citation check of KT-1322 now reads a
  document without Kronn's own partial-step banner, whose quoted gate reason
  (`[src: file: path:line]` turned round and cut at its limit) was refused as a
  `(src: file: …)` the step never wrote.
- An audit agent that outlived a backend stop no longer keeps writing after
  boot settled its step, and a resume no longer warns that « the sources
  changed » when they did not (KT-1324). Agents run through the ACP adapter
  (Claude and Codex by default) have no pid recorded on their run, so boot had
  nothing to stop: it settled the step as « it had written nothing » and the
  orphan then edited `docs/` for minutes. Every process an audit run starts now
  carries the run's tag in `KRONN_TASK_DISPATCH_ID`
  (`audit-run:<instance>:<run id>`, the variable `core::dispatch_reaper`
  already stops task workers by; the instance is a hash of the data directory,
  so a bench backend on a copy of the database never stops the live agents),
  whichever route launched it. Boot stops what carries the tag of one of its
  own runs (the recorded group first), and only then settles the steps that
  never ended; a run something of which survived the reap, or could not be
  looked for (on Linux, a process of the user's own that `/proc` will not
  show), is left unsettled rather than judged on a file still being written.
  Agents started under the former tag (`audit-run:<run id>`) are looked for
  too, so an upgrade does not let one write past a settlement.
  Every launch that takes snapshots or rewrites docs (full audit, partial
  refresh, revalidation, including a revalidation that only re-anchors
  citations) first stops the agents of all the project's open runs and
  settles their unfinished steps, and refuses to launch while one may still be
  alive. The refusal names the process and how to stop it (`kill -9 <pid>`);
  when Kronn cannot read the process table (a restricted `/proc`) it says so
  and offers an « I'm sure none is running: continue » action, on the audit
  timeline and in the revalidation panel, that repeats the launch with
  `accept_unverified_agents: true`, the human's word that none runs, which
  never covers a process Kronn saw. The flag is a human-only API field: no MCP
  tool carries it. The refusal's SSE `error` event carries a typed `error_code`
  (`agents_unverified` for the unreadable table, `agents_still_running` for a
  process that was seen) on every path, and the UI reads that code, not the
  message text; a retry never resends the flag, even after a confirmed launch
  that failed again. The
  sources' print a run records is now read once Kronn's own
  install has written its root `AGENTS.md` pointer: taken before it, it
  differed from the next run's and every resume of a freshly started audit
  warned.
- An audit TD card no longer fails for the whole batch when one TD cannot be
  written (KT-1348, GitHub #258, reported by tarussio). A batch is now partial
  by design: every TD that can be recorded is, and the card stays pending,
  narrowed to the ones that were not, each named with its reason on the card.
  Once the sheet is repaired, answering again applies only those (no TD is
  written or accepted twice) and the validation resumes after the last one. A
  card where nothing could be recorded stays pending whole and names each TD.
  The reasons live in their own `td_errors` field on the card (the agent's
  `context` is never cut or mixed with them, even at its 4000-character limit),
  are replaced by each new partial, and disappear once the card is answered;
  their history stays in the partial receipt messages.
  A sheet whose status lives only
  in `audit_history`, and a TD only the index lists, are resolved; and
  answering one card no longer makes a later row-only TD of the same
  validation unresolvable (the index resync keeps such a row as a bullet,
  which is now read back).
- HTTP agents (LiteLLM, OpenRouter, NVIDIA, Ollama…) obey the same access rule
  as CLI agents (KT-1346). Their native tools ran in process without the bridge
  token's gate: one could create, edit, block or read the planning tasks of any
  project, save a Quick API or Quick Exec in another project or move one there,
  and read any media job. Their run now holds a bridge token, and each tool
  that touches a Kronn object is decided and scoped by the same code as the
  route it mirrors: its room's project, the room's grants for the room's own
  agent, the validated workflow scope for a step, nothing more for a worker.
  A creation with no project lands in the room's project, as for a CLI agent,
  and a project room's agent no longer edits a general workflow or Page. A
  room without a project reads the general resources; its agents (CLI and HTTP
  alike) create general Quick Prompts, Quick APIs, Pages and workflows only
  once a human granted it « General · write » in its Scope settings, behind a
  typed confirmation, shown in red in the header and revocable (migration 270);
  they then edit only what the room created, recorded at creation (migration
  271), and every other general resource stays a human's to edit. A tool with
  no access rule is refused. A CLI agent may now name a media connection by its
  alias or name, as the media route already allowed.

- Retrying a step, or resuming an audit, from the UI keeps the run's tier
  (KT-1320). The card's picker defaulted to `reasoning`, so the retry of a
  Haiku (economy) audit ran on Opus. The run now records its tier and
  connection (migration 268, `audit_runs.tier` / `connection_id`); a retry or a
  resume sends the run's agent, tier and connection, and the panel shows the
  tier it will use, until the human picks another. A resume request that names
  no tier takes the run's, not Reasoning.
  A connection is inherited only by the same agent; if the run's connection
  was deleted since, the agent's default takes over and a warning says so,
  also when the client echoes that connection id back (the UI leaves it to the
  server while its picker is untouched). The run's agent and tier are kept
  whatever its connection does: a connection list still loading or failed to
  load is "unknown", not "deleted", and a deleted connection (also a Custom
  one, or one whose provider has another connection) shows a note before the
  launch — the run goes on the agent's default connection, never on another
  agent. The full, partial and revalidation launches all run on the choice the
  panel shows. A pick is forgotten when the project or the run to continue
  changes.
- A finding that a file exists or is tracked can be cited without a line
  (KT-1320): `[src: file: .ssh/key:exists]` (or `:tracked`) passes the
  documentary gate for a text file, so a tracked private key or a
  `yarn-error.log` no longer has to be cited `:1`, which invited quoting a
  secret. A bare path for a text file is still refused — a claim about its
  content needs a line — and the refusal now names the presence form.
  `:tracked` is checked against the git index (`git ls-files`), not only
  against the disk; a file that exists but is not tracked is refused.
- A bridge-token 403 for a discussion outside the agent's scope now says why
  and what to do (KT-1320), instead of reaching a Vibe/ACP agent as a bare
  "the MCP tool returned an error": ask the human to attach the discussion, or
  use one of the project's discussions.
- The tech-debt review keeps the sheet valid, keeps the "why" and counts the
  debts the card counts (KT-1309). Saving a decision appends its
  `audit_history` entry with the indentation of the list it joins, so a
  standard YAML parser (Ruby, `serde_yaml`) still reads the header; the typed
  reason is the entry's note for Confirm, Defer, Reject and Keep on purpose
  (the fixed note only stands in for an empty one) and the field's label says
  so. The review lists every sheet the card counts, including the `CQ-*` and
  `PERF-*` detail files an audit wrote without front matter (severity and
  status from their index row, else Unknown): a Critical or High one blocks
  the validation gate until it is settled, and settling one gives it the
  front matter and `Status` line it lacked. The generated tech-debt index
  lists them too, so their severity survives a resync. The impact is rendered
  as Markdown and a sheet with no verification criterion says so. Card and
  review read one set of debts (`td_index::debt_entries`): settled ones
  (rejected, kept on purpose, or marked resolved in the index of a sheet with
  no status) drop out of the card's count and show as settled in the review,
  the block header shows the same active count, and a `TD-*` row of the index
  with no sheet file yet is listed and can be decided (the decision writes its
  sheet from the row). The history writer also handles a trailing comment on
  `audit_history:` / `metadata:` and a flow-style `metadata: {…}` without
  duplicating the key (a flow mapping that already holds an empty
  `audit_history` gets the entry in that key, written once), reads a `#` inside
  quotes as text rather than a comment, refuses an `audit_history` that is a
  non-empty flow list rather than corrupting it, and keeps a new reason typed
  the same day for the same status instead of dropping it. A project with only
  the central index (no `tech-debt/` folder yet) can decide a row that has no
  sheet: the first decision creates the folder inside the docs root, and a
  `tech-debt` link leaving it is still refused.
- Revalidation after an interrupted audit (KT-1312). A refresh or a
  revalidation is no longer refused once every failed step of the interrupted
  run is accepted (`remaining_failed: []`): a resume would only finalize it.
  A resume never runs an accepted step again, whatever blocker its document
  still holds; only `redo_steps` does. The docs a step rewrote are
  re-fingerprinted as soon as the step ends, so a chain of resumed runs leaves
  none with the fingerprints of an earlier audit. A doc a failed step left
  under its partial banner stays in the revalidation plan (its owning step, or
  a manual line) and keeps the badge raised, though relevance sees nothing
  wrong with its citations.
- Kronn no longer cuts a text inside a `[src: …]` marker, and the detector
  gate retries (KT-1323). The TD index cut a long title at 160 characters
  inside a citation; the cut opener then swallowed the next `]` of the index,
  the end-of-run gate blamed step 8, and the regenerated index failed again,
  for ever. Every Kronn truncation that can hold a citation (index titles, TD
  task text, banner and stored reasons, the briefing excerpt) now goes through
  `truncate_outside_markers`, which stops before a marker the limit would
  split. The detector disposition gate no longer fails step 8 outright: it
  gets the bounded retry of the other gates, with feedback naming the signals
  to dispose of. The hygiene detectors (`_blank`, zero-tests, template
  checks) skip generated, vendored and framework scaffolding files
  (`generated/` dirs, root-level `coverage/` / `Pods/`, minified bundles,
  Symfony `web/config.php`, `.gitattributes` `linguist-generated` /
  `linguist-vendored` with last-match-wins and negation), so they no longer
  force TDs; the security detectors, the Docker/CI inventory and a team's own
  bans still see every file, so a `.gitattributes` line can never hide them.
- Agents now learn when and how to ask for a human arbitration (KT-1329).
  Claude Code cuts the `kronn-internal` MCP `instructions` at 2 048 characters,
  and the `kronn-question` guidance started at character 1 985 (cut to a single
  sentence, no schema) or, on a first session, at 2 438 (cut entirely). It is
  now a self-contained bullet with a valid inline example right after the intro
  line, followed on a first session by a compact FIRST CONTACT line that points
  to `kronn_intro` (also inside the cut), and a Python test pins both
  onboarding states. A workflow step that can `disc_append` gets the bullet too; a spawned
  task worker is told why it cannot post a card and to ask its principal. The
  card is also named in the `disc_append` description, the `disc_join` next
  steps and the project template, the native prompt triggers on any decision
  the human must arbitrate, and the `structured-questions` skill keeps
  `{{var}}:` for facts only. To keep `templates/docs/AGENTS.md` under its
  800-word budget, two "Do NOT" lines were dropped from its § 2 DO NOT (new projects
  no longer get "Load all T2 files at once — max 3, pick what you need" and
  "Skip the test policy — see § Project parameters and § 4"); the test policy
  itself is still stated in § 4 and the Do list.

- The documentary gate no longer refuses what Claude Code's Read tool shows
  (KT-1322). A newline-terminated file of N lines is numbered up to N+1 there
  (the last line is empty), and a range ending at N+1 was refused as outside
  the file; it is now accepted by every range check (the gate, the
  anti-hallucination verifier, citation drift and the bundle repair), through
  one shared helper. The gate also stops suggesting what it refuses: a
  comment after a non-numeric line (`docs/AGENTS.md:Focused on x`) is no
  longer split into `[src: file: docs/AGENTS.md:Focused]`, an empty file gets
  no `1-1` range, and a secret-looking file (`.ssh/…`, `.env`) is never
  offered a range to cite. A parenthesised `(src: file: …)` citation, which
  nothing verified, is refused with the bracketed form to write; a broken
  relative link names the path it resolves to and the link that exists
  (`../tech-debt/TD-1.md` from `docs/` → `tech-debt/TD-1.md`). Review
  follow-up: citation drift hashes and evaluates a range ending at N+1 (it
  reported it « removed » on the next re-audit), a range that starts on the
  empty line N+1 is refused everywhere, the verifier no longer rounds an empty
  file up to one line (`empty.txt:1` is refused), a text file given without a
  line is offered `path:<line>` instead of a bare path, and the secret-looking
  list now covers `id_ecdsa`, `id_dsa`, `*.p12`, `*.pfx`, `.netrc`, `.npmrc`
  and `.pgpass`.

- The dependency watch no longer reads only `node_modules` (KT-1325). Without
  an install, the version in place comes from the lockfile — `package-lock.json`
  / `npm-shrinkwrap.json` (v1, v2, v3), `yarn.lock` (classic and berry),
  `pnpm-lock.yaml` — or `composer.lock` for Composer, so a checkout with a
  lockfile no longer shows « declared only ». The source (lockfile vs
  installed) is carried in the snapshot, the audit prompt's dependency table
  and the `deps:` citation verdict, and the overview names the lockfile. Every
  manifest the watch cannot settle (missing binary, network, parse error,
  timeout, nothing installed) carries its reason in the snapshot, the audit
  prompt block and the overview, instead of a bare « Error ». KT-1279's
  workspace handling is unchanged. Review follow-up: a reason quotes what the
  tool printed, so credentials are masked before it is stored, shown or put in
  a prompt (URL userinfo and query, `_authToken=`, bearer headers, `ghp_` /
  `npm_` tokens); a root lockfile resolves a nested project only when the
  root's `workspaces` globs (or `pnpm-workspace.yaml`) list it; a Composer row
  read from `composer.lock` is labelled « from composer.lock », no longer « not
  installed ».
- A project validated a minute ago no longer reads « Validé, modifié depuis »
  with docs to re-check (KT-1319). Relevance judged Kronn's own TD sheets as
  claims: a `Next step` section (and `Remedy` ones) is now a proposal, and a
  path said missing right where it is cited (« the Jenkinsfile references the
  missing `config/x.yml` », « `config/x.yml` is missing from the repo ») no
  longer counts as a claim that the file exists. A « missing » elsewhere in
  the sentence does not exempt it, and `Mitigation` / `Recommendation`
  sections stay checked. The validation also stamps the docs
  no citation anchors as re-read, so their steps are not queued again right
  after it with no change to the code or the docs.
- The audit's citation gate now says how to fix a citation (KT-1307). A path
  that only resolves under the docs root or next to the citing doc gets
  « paths are repository-root relative; did you mean `docs/AGENTS.md:94`? »,
  and an out-of-range line reports the file's line count. Ranges accept the
  en and em dash, and a non-numeric line says « not a line number ». A bare
  path to a text file no longer tells the model to drop the claim: it proposes
  the whole-file range `path:1-N` (N filled in) to cite an absence. The dash
  normalisation lives in one place shared by the verifier, citation drift and
  the documentary gate, so `path:17–23` is no longer fabricated under enforce.
  Hint paths are canonical and only name a file that holds the cited lines; a
  `#symbol` anchor is accepted after a range; a whole-file range backing a
  claim that is not an absence raises a non-blocking `whole_file_citation`.
- After an accepted template v2 merge, the audit starts and the merge offer
  ends (KT-1310). Accepting files now records them as Kronn's in the folder's
  `.kronn.json` (committed with them, with the template install), so once the
  merge branch is integrated the audit gate no longer refuses the folder as
  "files Kronn did not write"; the team's sources stay theirs. The leftover
  merge worktree and branch are removed when the integration is noticed (on the
  next merge start or full audit), and a worktree with nothing left to review
  no longer reads as a merge in progress. `briefing.md`, `.kronn.json` and the
  run state are not docs to commit nor sources; a resumed or later plan no
  longer reads the files an accepted merge wrote as sources. A fresh clone of a
  v1-documented repository (entry file opening on `kronn:doc-version` and
  Kronn's heading, no `.kronn.json`) is Kronn's. Kronn's v1 template text
  (`How to fill`, `Examples (good shape …)`, `{{PLACEHOLDER}}` rows, the v1
  spec file) is left in place instead of copied as team content, copied
  headings nest under the target's own, a plan on the remote base warns about
  local docs it cannot contain (worded by the UI in the user's language), and
  the pending-integration refusal carries
  `error_code: template_merge_pending_integration`. Git failing to tell
  whether the merge branch is integrated (e.g. a dangling `origin/HEAD`) counts
  as not integrated: the branch is never deleted by guess and the audit refusal
  stays.
- Installing the template, then launching the full audit, is no longer refused
  as "files Kronn did not write" (KT-1299). `POST /install-template` now records
  the folder as Kronn's (`template_installed_at`) unless it already held a
  human's docs, so the template's own files are never counted as foreign. The
  briefing's "move to legacy" choice now moves a human's docs even when their
  entry file carries Kronn's signature (it used to skip silently and leave the
  launch blocked), and keeps `briefing.md` and `.kronn.json` in place. The
  partial audit and the revalidation hold on a human's docs the same way the
  full audit does (`docs_folder_choice_required`). The install records every
  file it wrote in `.kronn.json` (`kronn_written`, one reusable
  `record_kronn_written`): beside a human's docs those files are never moved to
  `legacy/` (only the human's are), and they never make the folder Kronn's —
  not even next to an earlier recorded audit. A recorded file counts as Kronn's
  only while its sha256 still equals the recorded one: a file the human rewrote
  (or a hand-crafted `.kronn.json` entry) is theirs again, counted by the gate
  and moved on the choice. The install refuses a corrupt `.kronn.json` before
  writing anything, and records a renamed template file under its new path.
- Revalidation no longer reads a remediation proposal as a claim (KT-1313). A
  TD sheet's `Remediation` / `Fix` / `Next steps` section, or a
  `- **Remediation**:` / `- **Suggested fix**:` line (and the text under a
  label that closes its line), names files still to be created: they are no
  longer judged by whether they exist, so `helpers/lib/plugin-factory.js`
  does not turn its sheet `no_longer_relevant`. A section left with nothing
  else to verify says why in its proposed correction.
- A stopped audit step keeps what it consumed (KT-1313). Its tokens join the
  run's tally and its reported cost is recorded (both pipelines); a Claude
  step's turns are summed as they arrive instead of keeping only the largest,
  the final `result` line replacing the sum; and `total_tokens_so_far` never
  goes down (it read 630, 877, then 346).
- The default-root audit golden no longer depends on the day it is run
  (KT-1317). The name of a `validated-originals/<hash>.bak` file is the hash
  of bytes holding the audit date; the golden now names it by the hash of the
  date-masked bytes (KT-1278). A new test replays the same audit 40 days
  earlier and requires a byte-identical snapshot, with no non-volatile entry
  relaxed.
- Orchestration live-test findings (KT-1316). A worker's reply is priced with
  the model that served it (`claude-haiku-4-5-20251001`), not the tier alias
  the discussion stores (`haiku`), which names no rate: its cost is no longer
  « unknown », and the delivery report shows the cost and tokens of the
  worker's replies (nothing when one reply is unpriced, never a partial sum).
  The « Delivery accepted » report is posted when the integration lands, not
  at approval: a delivery bounced by a merge conflict or a red validation never
  shows one. The orchestrator's room messages (worker brief, review request,
  escalation, control offer, send-back, timeout alerts, hand-offs, campaign
  gates) follow the principal discussion's language, English or French,
  instead of always French; a few fix hints and a handful of rarely-seen
  notices are still French. The Stop guide now says a clean worktree is
  removed by default (`remove_if_clean`) and the branch is kept. The scanner no
  longer logs a `resolve_host_path … contains '..'` warning per worktree: a
  worktree registered with a relative path is resolved against its
  registration. The MCP bridge script is looked up next to the running binary
  and in the checkout it runs from, never at the path of the checkout it was
  built in.
- Audit step 8 accepts a Dependencies row that states its source in any
  of its forms (KT-1295). The coverage check demanded the literal
  `source used:`, so a row backed by `[src: deps: …]` citations, or written
  `source: …`, was refused and failed the step after the retry. It now passes
  with `source used: …` or `source: …` (any case), a well-formed
  `[src: deps: <manager>/<package>@<date>]` citation (the citation gate still
  checks it against the watch table), or `not verified: no source`; a row
  with no source still fails, and the refusal names these forms. The
  `scanned — nothing substantiable` outcome also takes an en-dash or `--`.
  `resource used:` no longer passes for `source used:`. A `deps:` citation
  whose manager label holds a `/` (the npm label is `JS / TS`) is now
  verified against the watch table instead of being read as manager `JS`:
  every citation the snapshot emits verifies against it.

- The Full audit's documentary gate blames the step that introduced a blocker
  (KT-1306). A document no step targets (a tech-debt detail sheet, a workflow
  page) used to be charged to the founding step, so a resume re-ran the most
  expensive step for another step's file. Kronn now diffs the blocking
  diagnostics around each step (`.kronn/audit/document-blame.json`) and the
  final gate, and a resume, redo the introducing step; the baseline is taken
  before any write of the step, the cross-agent path's included, so what that
  path writes is charged to its step. A step is also blocked
  only by diagnostics absent before it ran: those already there are reported in
  a `documentary_preexisting` event instead of failing it.
- A doc's citations are no longer lost to a truncated marker, and a citation
  of an unknown kind is no longer judged as a file (KT-1311). The partial
  banner cut its reason at 240 characters, sometimes inside a quoted
  `[src: …]`, and the scanner then read the rest of the doc as one marker:
  no fingerprint was recorded, so drift went unseen. The banner now writes the
  reason with round brackets, and the scanner closes an unclosed marker at the
  next `[src:` or blank line and carries on. `[src: command: git log …]`
  (a kind on a closed list of non-file kinds: `command`, `shell`, `test-run`…)
  is unverifiable and counted in `unverified_count`, not a missing file, so
  relevance stops calling its section outdated. Any other word (`fil:`,
  `path:`, `source:`) is not a free pass: a path-like text is still verified as
  a file and is fabricated when missing, so the enforce gate keeps blocking it. Line references written in
  backticked prose are not re-anchored; only markers are (documented in
  `docs/conventions/agents-md-format-v1.md`).
- An ApiCall no longer masks a plugin's public config in the data it hands on
  (KT-1249). The base URL (`https://acme.atlassian.net`) was scrubbed as a
  secret since 0.14.3, so a Jira description reached the next steps with
  `***/browse/EW-1` links, and a PUT would have written them back broken. A
  config key declared `public` by a registry plugin (Jira's `JIRA_URL`) and a
  plugin's base URL when it is a bare origin (scheme, host, optional port, no
  path) are now left intact. Everything else stays masked, including any URL
  with a path (webhook URLs hide their secret there), whatever its key name:
  tokens, passwords, auth headers, the decoded Basic pair, every other config
  value, in the data, summaries, errors and logs. An imported bundle or a
  custom API cannot declare a value public. The URL decision is made on the
  raw text: a secret hidden behind dot segments (`…/SECRET/..`,
  `…/SECRET/%2e%2e`) no longer passes for a bare origin. A value declared
  public is checked too, even with no `/`: user info, query, fragment, `;param`
  and non-ASCII characters keep it masked (`https:user:SECRET@host`,
  `host;SECRET`).
- A review-wait timeout no longer locks the principal out of the review
  (KT-1293). The watchdog used to move the unreviewed delivery to `Escalated`
  while its alert told the principal to call `task_exec_review`, which only
  accepts `AwaitingReview`. The execution now stays `AwaitingReview`
  (`recovery_reason` `review_wait_timeout`, the clock disarmed so the alert is
  posted once): the principal can still approve (integration runs normally) or
  request changes (the worker gets the comment) on the unchanged delivery, and
  `task_exec_status` keeps naming `task_exec_review`. A total-duration deadline
  that expires on a delivered attempt takes the same road
  (`total_duration_timeout`, one alert, provider run left alone) instead of the
  silent `Escalated`. Because the row is no longer `Escalated`, an overdue
  review no longer puts the campaign in `AwaitingHuman` nor blocks scheduling
  (the escalated counts and `sync_campaign_human_gate` no longer see it).
- An audit step run through the ACP route runs instead of asking the human
  what to do (KT-1278). An ACP session has no system channel, so the context
  and the step reach the model as one message, and a step prompt that ended on
  a reference block (the runtime versions table, the briefing, the project
  rules, the detector signals, the linked repositories) was read by a small
  model such as Haiku as context to answer: it replied « What would you like
  me to help with? » and wrote nothing. Every step of every audit kind, the
  retries with citation feedback now end on a closing directive that names
  the step and its target file, says no human answers in that turn, and stops
  once the file is written. A cross-agent detection pass writes nothing, so it
  gets its own closing (answer with the findings block, write no file) instead
  of the step's « write the file ». A workflow step that declares read-only
  repositories no longer ends on that list either.
- The version watch no longer tells the audit a JS project is up to date when
  it could not look (KT-1279). A `package.json` without `node_modules` got
  `npm outdated --workspaces`, which prints `{}` and exits 0 without querying
  the registry, and that read « up to date » so the Renovate fallback never
  ran: the workspace flags are now passed only to an installed tree, and an
  empty answer for a manifest that declares dependencies but has none
  installed is the new `NotInstalled` status (incomplete, Renovate decides,
  listed as unchecked in the audit block). A Renovate package file with no
  resolved dependency (none listed, skipped, failed lookup) no longer
  finalizes as up to date either. The runtime pins also read a reusable
  workflow's `nodeversion` / `node_version` input (and its `default:`), a
  Jenkinsfile's `nodejs '…'` tool and node-versioned agent label, and a
  Dockerfile's `FROM node:<v>` / `ARG NODE_VERSION`; when nothing is parsed the
  audit block says « None parsed by Kronn » and lists the CI files present
  instead of « None found ». Review follow-up: a package npm lists without an
  installed version is flagged `declared_only` and shown as « not installed »,
  never as the version in place; a `node_modules` holding only `.cache` / `.bin`
  is not an install; a Jenkins label needs `node` / `nodejs` directly followed
  by a major (`build-node-1` is a host); a Dockerfile pin needs a leading digit
  and a multi-stage file marks its last stage as the runtime one; Renovate
  warnings no longer drop resolved updates.
- What a task worker starts ends with its dispatch, and Vite runs in its lane
  (KT-1266). A backend or a Vite started from the agent's shell tool ran in a
  process group of its own and kept listening on the lane's ports after the
  turn; the worker's processes now carry their dispatch id and every process
  carrying it is stopped when the dispatch completes, fails, is cancelled or is
  found dead, with the descendants that stayed in its session (what a shared
  daemon adopted in a session of its own is left alone). A pid is signalled
  only after its start time and its tag or session are checked again, so a
  reused pid is never touched. `frontend/node_modules` of a lane is a real
  directory of links, so Vite and vitest write their config bundle there
  instead of failing with EPERM on `node_modules/.vite-temp`, with no flag;
  the smoke check loads the config under the worker sandbox profile (it runs
  worker-written code), and the brief says how to recover.

- Cancelling an execution no longer resets its task to `todo` (KT-1267). A
  task whose work was integrated by hand (KT-1250's `3dcdbc892`) fell back to
  `todo` the moment its execution was cancelled, so the roadmap counted it as
  not started. The cancel now leaves the task status alone, whatever it is
  (`in_progress`, `done`...): the principal decides. The cancel result and the
  `task_exec_cancel` description say so, and that running the task again means
  setting it back to `todo` first (`task_update status=todo`); the launch
  preflight refuses any other status, so a cancelled `in_progress` task is no
  longer put back in the ready queue automatically.
- A task worker's lane build runs under the worker's sandbox profile
  (KT-1255). The first `cargo test --no-run` and the toolchain smoke check were
  started by Kronn as the host user, so a relaunch that rebuilt after the
  worker changed `Cargo.lock` or wrote a `build.rs` would have compiled
  worker-written code with the host's rights. They now run under macOS
  Seatbelt (Linux: bubblewrap), deny by default: writes only under the
  worker's write roots and the lane's own temp dir (no global `/tmp`), no
  outbound connection of any kind (loopback and Unix sockets included; only a
  loopback bind), Mach lookups limited to four named services (so no
  `launchctl submit`, `open` or XPC), and on Linux fresh network, PID and IPC
  namespaces with `/run` and `/tmp` emptied. A host with no usable sandbox
  refuses the build and says so, it never runs it unconfined: the first build
  and the smoke check are then recorded as failed and the worker launches cold.
  The probe also starts in a session of its own (`setsid`), with no controlling
  terminal, and the profile denies read, write and ioctl on every terminal
  (`/dev/tty`, `ttysNNN`, ptys, `/dev/console`) and grants no `pseudo-tty`: a
  `build.rs` cannot push keystrokes (`TIOCSTI`) into the operator's shell when
  the backend runs in a terminal.
- The principal's persisted validations run under that same sandbox (KT-1268).
  At integration, `cargo test`, `vitest` and the `cargo metadata` that sizes
  their target ran through Quick Exec as the host user in the worker's tree, so
  a changed `build.rs`, proc-macro, test or `.cargo/config.toml` ran with the
  host's rights. They now use the lane's profile and launcher (own session, no
  terminal, lane environment, process group killed on timeout): no write
  outside the worker's roots and the lane's temp dir, no connection, loopback
  included. A host with no usable sandbox — no mechanism, or a launcher such
  as `bwrap` that is missing or cannot apply its sandbox (a container) —
  refuses the validation naming that launcher, recorded as a failed validation
  and sent back to the worker, and never runs it unconfined. The lane's first
  build and the toolchain smoke check (KT-1255) go through the same detection,
  so on such a host they report that reason instead of `cannot start bwrap`.
  A validation that needs the network or a path outside the lane
  (a `git` write, an explicit `--target-dir` elsewhere) now fails there.

- A task worker's lane target (`target/wt-<lane>`) is removed with its clean
  worktree (KT-1256). It lives outside the worktree, so integration and
  cancellation never took it, and each one is a copy-on-write clone of the main
  target that turns into real disk once the main target is rebuilt. Kronn now
  stamps every lane it provisions with `.kronn-lane.json` (the execution it was
  made for) and only ever deletes a stamped lane: a `wt-*` directory without
  the marker (a human's or another tool's) is reported and never touched. At
  boot a stamped lane is removed only when its execution is finished and its
  worktree is gone; a finished execution's dirty, preserved worktree keeps its
  lane. A running Cargo build always refuses. `GET /api/maintenance/lane-targets`
  is the dry run (what is reclaimable, claimed, kept or not Kronn's, with
  sizes), `POST /api/maintenance/lane-targets/reclaim` removes the named lanes
  after re-judging each, and the boot report lists the same. Every removal is
  audited as `lane_target_reclaimed` / `lane_target_refused`.
- An approve that integrates now ticks the Definition of Done items it
  verified and stores their evidence on the item (the principal's
  `dod_verifications`, else the worker's own `dod_status`), so a task no longer
  reaches `done` with its DoD at 0/N while the evidence exists. A task with an
  unticked worker item is held BEFORE the git apply (the target does not move);
  the only exception is a KT-1254 `post_integration` live check, which an
  integration never ticks and which keeps the task open. `task_exec_deliver` and
  `task_exec_review` name the fields the backend requires (`tests_executed`,
  `reviewed_head_sha`, `dod_verifications`, the string version `"1"`), and a
  spawned worker's `tests_executed` schema is declared in the measured `TOOLS`
  literal.
- A project's `token_override` (a raw token) is no longer returned to a
  Kronn-launched agent: credential fields are stripped from every bridge-token
  response.

- A task linked to rooms of several projects is visible from each of them
  again (KT-1298, GitHub #248). One link to another project's room hid the
  whole task, so `plan_get` answered 403 in every room and agents duplicated
  the shared parent task. The links an agent cannot see are now left out of the
  task instead.

- The default-docs-root golden no longer depends on the day it runs: the
  compact date every step prompt opens with is masked like the dashed one.

- A Full audit installs its template from what the AI doc folder holds, not
  from the project's displayed status (KT-1204). An empty `docs/`, or a folder
  Kronn's docs left behind, read "template installed" and got no template, no
  anti-hallucination spec and placeholder prompts for missing files. An empty
  folder now gets the template, Kronn's own docs get the files they lack (never
  over existing ones), and a folder of human docs is refused before the run
  starts, with the same answer for the UI and for MCP `audit_launch`. A project
  documented in `doc/` or `ai/` is audited there instead of in a second
  `docs/` tree, and a legacy `ai/index.md` layout is asked to migrate first.
  The install records the folder as Kronn's in `.kronn.json` before step 1, so
  a first audit that crashed or was cancelled relaunches fresh without the
  folder question; a folder with Kronn's structural markers and a Full run this
  instance recorded for the project counts as Kronn's too.

- A native agent's reply to a joined CLI now reaches that CLI (KT-1201). When
  the room's agent answered a CLI turn without naming it, the reply had no
  target, and the bridge reported it as `withheld_by_routing` ("target another
  identity"): the CLI thought the agent silent for hours. The reply is now
  stored with that CLI as its target unless it names someone else, and the
  delivery treats an untargeted agent reply to a session's own message as
  addressed to it (`attention_required`). A native agent that writes
  `@claude` while a Claude CLI is joined (the author of the turn it answers,
  or the only one) now reaches that CLI instead of spawning a one-off Claude,
  which used to answer as if it were the CLI. A one-off agent is told it is
  not the joined CLI of its provider, whose turns it now sees labelled with
  the CLI alias. Joined CLIs need an MCP reload to load the new bridge.
- The MCP `audit_status` tool reports where a project's audits really stand
  (KT-1210). Its `latest` was the newest Completed run, so after a Cancelled,
  Failed or step-0 Interrupted run an agent read « Completed, nothing to
  resume ». `latest` is now the newest run of any status, next to
  `latest_completed`, `steps_to_redo` and `lease_orphaned` (launches refused
  until the cleanup action), read even while the tracker has a live entry.
  `GET /api/projects/{id}/audit-latest` returns these four fields.
  `audit_launch` takes `fresh` and `retry_steps`, the parameters the backend
  refusal already asked for, and refuses contradictory combinations before
  any call. The bridge is a long-running process: reload it to get the fix.
- An agent can open the images and videos it generated (KT-1202). They were
  stored in Kronn's data dir, outside the project the CLI agent is allowed to
  read, so its `Read` of its own picture failed. Generated media are now
  stored like uploads: the copy the UI serves stays in the project's main
  checkout under `.kronn/context-files/` (Kronn's data dir without a
  project), so removing a worktree never deletes it. When the agent runs
  elsewhere (a worktree, a project-less discussion's folder), it gets a
  git-excluded copy under its own `.kronn/context-files/` and that path in its
  prompt; a failed copy is logged and the original path given instead. Media
  already stored keep working: files are served from their recorded path.
- « Recréer les fichiers du template » now ends with a next step (KT-1203).
  The recreated files are raw templates, so they hold `{{PLACEHOLDERS}}`
  until an audit fills them. The result lists each doc that still holds
  placeholders, maps it to the audit step whose target it is, and offers one
  button that runs the partial audit for exactly those steps (same lease and
  saved briefing as « Mettre à jour »). Docs that no audit step targets are
  listed as such, with no button. If the docs entry itself was recreated, the
  project is back to `TemplateInstalled` and the result points to the full
  audit instead. The confirmation says that recreated files stay empty until
  those steps run. The project card shows the docs still holding placeholders,
  with the same action. The count comes from `GET /api/projects/{id}/drift`
  (`placeholder_docs`), so it survives a reload. It reads only the template's
  own files, and `TEMPLATE.md` templates are not counted.
- A stale-sections refresh that re-verifies a section without rewriting it
  now completes (KT-1207). It used to end Failed: the baseline stayed stale,
  the validation revoked at launch stayed revoked, and the validate gate
  refused the project until a whole 16-step audit ran. An `unchanged` step
  now counts as re-verified: the run ends Completed and its sections get a
  fresh baseline. Any partial run that rewrote nothing, stopped before its
  first write included, restores the `validated_at` it revoked. When no
  validation comes back, the re-verified sections get their own validation
  discussion. An interrupted refresh now keeps what it did: the baseline is
  merged for its done steps, and they get a validation discussion scoped to
  them, while the run stays Interrupted. A new « Vérifié, inchangé » button
  next to « Mettre à jour » marks the stale sections as still true without an
  agent (`POST /api/projects/{id}/drift/acknowledge`). It refreshes their
  checksum mappings, and their citation fingerprints when recorded, never
  rewrites a doc, takes the audit lease, and is refused to a bridge token.
- Tech-debt and cross-agent decisions now stick (KT-1208). Answering a
  cross-agent dispute card applies it under the project's audit lease:
  « dismiss » rejects the sheet, « keep » leaves it active, a refusal keeps
  it disputed, and the sheet's Kronn block, the step index and the central
  index say so instead of « human decision pending ». The project card shows
  the open disputes with a link to their discussion, and `validate-audit`
  refuses while one is open. Before a run Kronn snapshots the decided
  sheets; afterwards it puts back a rejected or accepted sheet the run
  deleted and any decision the run changed (a confirmed sheet the run
  deleted stays deleted, reconciliation reports it), and flags a new sheet
  that repeats a rejected or accepted one (same file, same problem). The
  cross-agent pass reuses such a sheet instead of minting an id, keeps the
  previous index's undetected rows, settled sheets and `owner="human"`
  sections, and reports a step that rewrote nothing as `unchanged`. The
  project badge and the health score count the same active sheets: rejected
  and accepted sheets and `_`-prefixed reports are left out of both. The
  Kronn task of a sheet that is rejected, accepted or removed is archived
  (annotated only when work on it has started). A partial refresh states, in
  its stream and its UI, that it runs single-agent when the briefing asks for
  the cross-agent audit.
- A favourite stays in Favoris while an agent runs or waits in it. From 20
  discussions on, a live pinned discussion moved to « À suivre » and left
  Favoris until the run ended. Favoris now lists every pinned discussion,
  live ones first with their indicator, and « À suivre » keeps the unpinned
  live and unread work, so each discussion has one shortcut row.
- Links inside project docs now open inside Kronn (KT-1200). A relative link
  such as `testing-quality.md` or `../tech-debt/TD-….md` used to go to the
  browser, which reloaded the app and opened nothing, since Kronn has no
  router. The doc viewer now handles every link itself. A link to another doc
  opens that doc in the viewer. The link is resolved from the current doc's
  folder, falls back to the repository root, and never goes above it. A
  `#section` link scrolls to the heading, since headings now carry slug ids.
  A link to a source file, with or without `:line`, opens the source viewer
  at that line. Web and `mailto:` links open in a new tab with
  `rel="noopener noreferrer"`. A link to a missing doc, or to a path outside
  the project, shows an inline message instead. A back button returns to the
  previous doc.
- Dialogs, drawers and click targets work from the keyboard (KT-1072). A
  shared `useDialogFocus` hook gives focus on open, keeps Tab inside, closes
  on Escape and returns focus to the trigger; it now covers the folder
  picker, the audit history drawer (now `aria-modal`), the API call log
  drawer, the tech-debt review and its source viewer, « À reconfirmer »,
  « Revalider l'audit » and the template reinstall confirmation. API call
  log rows and the doc viewer tree take focus and open with Enter or Space;
  the project card's validation, anti-hallucination and tech-debt badges are
  buttons, and the card header no longer swallows Enter or Space pressed on
  a control inside it. The audit history's sortable columns expose
  `aria-sort` and sort from a button, and the audit timeline announces each
  new step, not every tick, through a polite live region.
  The template reinstall confirmation is non-modal (Tab may leave it, only
  an Escape pressed inside closes it) and focus follows each step: the
  report's or the error's « Fermer » after confirming, then the open button.
  Escape in one of two side-by-side inline panels closes that panel. The doc
  viewer's source overlay is a modal dialog, the tech-debt review and its
  source viewer leave Escape to an IME composition or a control that
  consumed it, and one Escape in the commit detail closes it alone, not the
  viewer around it.

- One click on « Voir » in the pending-decisions banner now brings the
  decision card into view. It fired a single smooth `scrollIntoView`, aimed
  at the layout measured at the click; message rows use
  `content-visibility: auto` and take their real height only as the scroll
  reveals them, so on a long room it landed short and took up to ten clicks.
  The banner now uses the room's pinned navigation, which keeps the card
  centred until the rows above settle and stops on a wheel or touch. The
  asset inventory's « go to message » and the in-room search's
  previous/next had the same one-shot scroll and use the same pin.
- The linked-repos block of audit prompts and `docs/linked-repos.md` no
  longer claim that every Kronn-bootstrapped repo has a `docs/AGENTS.md`
  (KT-999): each entry states the entry point Kronn found in that repo
  (`docs/AGENTS.md`, legacy `ai/index.md`, README only, nothing, not checked
  for a URL, or path not found).

- Security: the citation verifier no longer marks a source outside the project
  `verified` (KT-1077). An absolute citation such as `[src: file: /etc/passwd:1]`
  used to pass on mere existence; it is now accepted only under a project root
  (the discussion's worktree, the checkout, a declared linked repo) and then
  jailed like a relative path, otherwise it is `outside_project`. Symlinks are
  resolved one component at a time without following one out of the root, so
  an existing and a missing file behind an escaping link get the same verdict:
  nothing outside the root is stat'ed or opened. A `[src: …]` marker quoted in
  inline backticks was skipped entirely; it is now verified too, green when it
  resolves, and counted as unverified (amber, never fabricated) when it does
  not, because docs quote the grammar itself that way (`[src: file: <path>:<line>]`).
  `docs/AGENTS.md` and the citation spec say exactly this.

- A stale-sections refresh launched after a Stop now runs (KT-1188). Like the
  Full audit, the partial pipeline clears a cancellation flag left by a
  previous run as soon as it holds the project lease, so a Stop nobody
  acknowledged no longer stops the next refresh before its first step.
- An audit no longer acknowledges the broken citations of a doc edited by hand
  (KT-1187). After a Full audit or a partial refresh, only the docs a step of
  that run proved it rewrote (its target, or a document its documentary
  repair rewrote) get fresh citation fingerprints. A doc changed outside the
  run keeps its record, so its broken citations stay « à re-vérifier » until
  a validation, an acknowledgement or a rewrite checks them. A doc no step
  wrote is no longer recorded by the run either.
- An audit error never stays on screen without a way out, and « Lancer
  l'audit » always starts from what exists (KT-1169). A launch that stops on
  an error, whether refused, cut off or offline, leaves a « Reprendre »
  banner in the timeline that replays that same launch. An unreadable
  timeline, recap or resume state says so with « Réessayer ». A refused
  acceptance keeps its form and reason. The server now refuses a launch
  without `resume_run_id` while the latest run can still be resumed, unless
  the request carries `fresh: true`, the confirmed « Relancer quand même
  tout l'audit » choice. A stale-sections refresh is refused in that case
  too. Bridge agents get the run id to resume. When the card cannot tell
  whether a run is resumable, launching waits, and a step retried alone
  never turns into a whole audit. On a documented repository no launch
  installs the template again. The states and their actions are listed in
  `docs/architecture/audit-run-states.md`.
- `PUT /api/projects/:id/briefing` is no longer ignored once
  `docs/briefing.md` exists (AUD-14): it writes the file, or removes it when
  the notes are cleared, before the database column the audit falls back to.
  The briefing injected into each audit step, full or partial, is capped at
  6,000 characters and ends with an explicit truncation marker that points
  to `docs/briefing.md`.

- A task execution's progress no longer exposes the raw name of the running
  tool (KT-1124). `task_exec_status` returned it in
  `TaskExecutionProgress.reason`, and an MCP name such as `mcp__github__...`
  reveals the server. The reason is now a tool category (`Read`, `Mcp`,
  `Kronn`...), stored as such and mapped at read time for rows written before.
  The tool transcript is unchanged.
- The `db-backup.md` cron recipe now creates the backup folder as `0700` and
  the copies as `0600` (`umask 077;`), with the `ls -la` result to expect
  (KT-1075). A copy holds the conversation history in clear.
- The audit UI no longer gets stuck (KT-1134). Every run state offers an
  action (see `docs/architecture/audit-run-states.md`). A first audit that
  stopped can be resumed from the validation phase, and an interrupted
  re-audit points validation to its failed steps instead of an unlinked
  discussion. Relaunching the whole audit over an interrupted run asks first.
  « Validation avec vous » is never « En cours » while an audit runs. A cancel
  the backend did not acknowledge stays « annulation en attente » instead of
  going idle. A cancel deletes only its own run's validation discussion,
  never an earlier run's. A lease whose run could not be settled is retried,
  then signalled with a « Débloquer » action wired to
  `/api/audit-runs/cleanup`, which now spares live audits. Validation agents
  are stopped when an audit starts and held while it runs, so they never
  write `docs/` at the same time as the audit.

- A file cited by an agent that exists only in an unmerged worktree of the
  project no longer shows as "not found" (KT-1184). Citation checks look in
  the main checkout first, then in the project's own worktrees (the folders
  under `.kronn/worktrees/` and the worktrees git registers for the repo,
  most recently modified first, symlinked entries ignored), with the same
  path jail and line checks. A path written with
  `.kronn/worktrees/<lane>/` always resolves in that lane, however many
  worktrees the project has. A path into another
  project's worktree is refused. Each stored source check gains an optional
  `origin` (`{kind: "checkout"}` or `{kind: "worktree", name}`); older rows
  without it read as checkout. In the message, a lane file gets its own
  neutral blue chip tagged with the worktree name, and its tooltip says it is
  present in that worktree but not merged yet. The footer counts it apart
  ("in a worktree"), and the detail panel lists it under its worktree. Red
  stays reserved for a file found nowhere. Lane files no longer count as
  fabricated in the lint report, so the `disc_append` receipt stops warning
  about them.
- The release body no longer mentions GitHub accounts by accident (KT-1179).
  Multi-agent syntax examples in the CHANGELOG (an `at` sign followed by an
  agent alias) were read by GitHub as mentions of real accounts, listed under
  Contributors and notified. `release_notes.py` now wraps every bare mention
  outside code, e-mail addresses and URLs in a code span, including one
  that follows a lone backtick. Real human contributors, listed at release
  time from the repository contributors API (bots dropped), stay mentions;
  the maintainer is the fallback when the lookup fails or returns nothing.


- The desktop app now applies the saved anti-hallucination mode at startup
  (KT-1149). Only the standalone backend armed it, so on desktop the setting
  was ignored until it was saved again in Settings. The arming moved into the
  shared startup config load, so both boot paths run it and cannot diverge.
- Creating an isolated worktree no longer edits the operator's checkout
  (KT-1125). It used to create or append to the project `.gitignore` to hide
  `.kronn/`, which left a new file or a modified tracked file in `git status`.
  The rule now goes into the repository's local `info/exclude` (the shared git
  common dir), once, and nothing at all when `.kronn/` is already ignored.
  The same holds for every other private path Kronn used to add to the project
  `.gitignore` (`.kronn/tmp/`, `.kronn/context-files/`, generated `.mcp.json`,
  `.kiro/`, `.gemini/`, `.vibe/`, `.ai/mcp/`, agent profile files, `docs/var/`
  on project status) and for the startup sweep over all projects.
  A `.kronn/` line that an older version already added to a `.gitignore` is
  left in place: Kronn never removes lines from your files.
- The legacy-docs check recognises the v2 template's `docs/AGENTS.md` again
  (KT-1172). It compared the first line with the template heading, but the v2
  file opens with `<!-- kronn:doc-version -->` comments, so a Kronn-managed
  `docs/` was taken for hand-written docs.
- After a Full audit, the central tech-debt index lists every TD sheet,
  including the ones the seven sub-audits wrote (KT-949). Bench runs had
  indexed 7 of 35, 6 of 37 and 13 of 57. Kronn now rewrites the
  `## Current list` table of `docs/inconsistencies-tech-debt.md` from
  `docs/tech-debt/TD-*.md`, without a model. Each active sheet gets one row
  with its title, area, severity and status, the dimension index that lists
  it, and a link to the sheet. Rejected sheets and accepted trade-offs are
  counted but left out of the table. A row written by hand for a TD that
  has no sheet is kept, and no sheet is deleted or merged. The table is
  rewritten the same way after each TD validation card. A
  `Dimension coverage` row that says `N/A` or `scanned — nothing
  substantiable` while sheets exist for that dimension now reads `findings`.
  The row names the TDs and keeps its previous outcome. A new `unindexed_td`
  warning reports an active sheet the index does not reach. The audit
  summary now counts the TDs of every sub-audit, not only the central rows.
  The sheets in `docs/tech-debt/` stay the source of truth.
- A second Full audit of a repository whose documentation is already up to
  date no longer fails in a loop (KT-1130). A step whose agent had nothing to
  change used to fail with "rewrote nothing", and "Reprendre" replayed it into
  the same failure, so the run never reached Completed. When every other check
  passes, the step now ends **Inchangé**: it counts as done, the run completes,
  a resume skips it, and the validation discussion lists these steps in their
  own section for the human to confirm. The timeline shows a neutral
  "Inchangé" badge and an info toast instead of a red failure. A target that
  still holds a placeholder, or that cannot be read, still fails. The outcome
  is stored on the step (`audit_run_steps.unchanged`, migration 245) and in the
  run's `step_outcomes_json`. A first audit of a blank repository also gets the
  anti-hallucination spec its `docs/AGENTS.md` links to, so the documentary
  check no longer blocks step 1. The outdated "no template to restore" advice
  is gone from the step-failure toast.
- A re-audit now re-checks the documentation it already wrote against the
  current code, instead of looking only for template placeholders
  (KT-1132). When the target of steps 1 to 7 is already filled, the step gets a
  re-verification prompt: check each fact and each citation against the code,
  correct what is false or stale, keep what is still true word for word,
  never touch `owner="human"` sections, and leave a still-accurate file as it
  is so that the step ends **Inchangé**. Before the agent runs, Kronn lists the
  target's citations that no longer resolve (deleted file, line past the end
  of the file) and hands them to the step. Such a document can no longer end
  unchanged: the step corrects it in the same run, or fails after three
  attempts, where it used to end unchanged and block the run at the final
  documentary check. A dead link or citation inside an `owner="human"`
  section is never handed to the agent and never blocks a step or the run,
  since no step may rewrite that section: it is reported as a
  `human_section_reference` warning naming the section, and the validation
  discussion lists it for the section's owner to fix. On a project already
  audited, the timeline's launch
  button reads "Revérifier la doc" and explains what it does before you
  start. A Full audit no longer writes `docs/var/` into the project
  `.gitignore`: it goes into the local `info/exclude`, like Kronn's other
  private paths (KT-1125), so an audit leaves your `.gitignore` byte-identical,
  or absent.
- `kronn dev` stops on Ctrl-C or TERM in every phase (KT-1145). Under bash
  5.2 the backend supervisor could drop a TERM that arrived while it expanded
  a command substitution, right after its Cargo build, and keep running. It
  also waited for a long initial build to finish before honouring the signal.
  It now uses no command substitution once its traps are set and runs the
  build in the background, so the signal stops the build at once.
- In a multi-agent room, an agent launched in parallel on the same message now
  knows who it is and who the message is for (KT-1153). Its prompt gives its
  room alias (`@codex`), the main addressee (the message's first target), and
  the sentences of the message that name it. It is told to answer in its own
  role, without taking over the main agent's or speaking for it, instead of
  "reply directly to the user". It also sees the whole agent thread under the
  message, replies to replies included, not only the direct replies. The
  deeper replies are capped at the 24 most recent and 24 000 bytes.
- An HTTP agent whose endpoint does not answer now fails in under a second
  with the VPN card, and the other agents of the turn start anyway (KT-1154).
  The start runs the KT-697 probe first (0.8 s budget, shared 30 s cache): a
  DNS, refused, TLS, invalid-address or connect failure refuses that agent
  alone, before any model call and without the runner's three retries. A
  timeout or a 5xx lets the run try, so a slow proxy is never refused. Off the
  VPN, LiteLLM used to show its card about 7 s after it was mentioned.
- A model the catalogue refuses before the start now leaves a System card in
  the discussion instead of a toast only. The card names the model and the
  cause (access denied, not found, gone from the catalogue, sign-in needed...),
  gives the date the catalogue recorded it, and the step that applies. The old
  "not found or access denied, choose another model" sentence is gone.
- The composer warns on the mentioned agent while you write: an endpoint the
  last probe could not reach, with the reason, or a model the catalogue
  refuses, with the date of that refusal. The probe runs when the set of
  mentioned agents changes, not on each keystroke
  (`POST /api/agents/reachability`).
- A Full audit no longer leaves its working files in your `docs/` (KT-1165,
  issue #231). The citation originals and the documentary optimization report
  now live in `.kronn/audit/`, which Kronn lists in the repository's local
  `.git/info/exclude` and never in your `.gitignore`, so `git add docs` picks
  up only documentation. Files an older version left in
  `docs/.kronn-citation-originals/` move there on the next audit, after a
  sha256 check of each one, and the old `docs/.kronn-document-optimization.json`
  is removed, as each audit writes it again.
- A full audit can now pass on a project made of documents (KT-1164, #226).
  A binary file (.docx, .pdf, image) has no lines, so it is cited by its bare
  path, `[src: file: Budget 2026.docx]`, and the documentary gate accepts that
  form. Kronn tells a binary file from a text file by its content, not its
  extension. A line on a binary file is refused with a message that says to
  drop the line; a text file still needs one. Percent-encoded links and
  citations (`My%20File.docx`) are decoded before they are resolved, and a
  path that leaves the project stays refused. The audit prompts, the repair
  messages and the citation spec now describe the same form.
- A native agent's `disc_append` during its own turn is checked to stay in
  that turn's thread (KT-1171). With no explicit `reply_to_message_id`, the
  post replies to the message that started its running job, so the other
  agents of the turn see it in their view of the thread. An explicit reply
  wins. A post from a joined CLI, outside a running job, or naming a job of
  another discussion keeps no inherited reply. The orphan reply seen in a
  0.14.3 room came from a backend older than this rule (KT-1151); new tests
  now pin each case.
- A Kronn backend started on its own `KRONN_DATA_DIR`, for a test or a bench,
  no longer rewrites the CLI MCP configs in your home directory (KT-1144):
  `~/.copilot/mcp-config.json`, `~/.codex/config.toml`, `~/.claude.json` and
  `~/.gemini/settings.json` are left as they are, and the log says why. The
  desktop app, `kronn start` and the Docker container sync as before, and so
  does a backend whose `KRONN_DATA_DIR` names the default data directory or
  that sets `KRONN_HOST_HOME`. `KRONN_HOST_MCP_SYNC=1` writes them anyway,
  and `KRONN_HOST_MCP_SYNC=0` still turns the sync off.
- The same isolated backend no longer writes your API keys into
  `~/.codex/auth.json` or `~/.gemini/settings.json` (KT-1162). "Sync agent
  tokens" and the OpenAI and Google override toggles answer HTTP 409 with a
  message naming `KRONN_HOST_HOME` and `KRONN_HOST_MCP_SYNC=1`, and change
  nothing. With `KRONN_HOST_HOME` set, both files are written under that home
  only; Codex used to ignore it outside Docker. The default instance syncs as
  before, `KRONN_HOST_MCP_SYNC=0` included, and "Sync agent tokens" now lists
  only the files it actually wrote.
- The Codex and Copilot syncs keep the MCP servers you added yourself. They
  used to replace the whole `[mcp_servers]` table and the whole
  `mcp-config.json`; Kronn now records the servers it wrote in
  `kronn-mcp-state.json` beside each file and replaces only those. Servers
  left there by an older Kronn whose MCP has since been deleted are not
  recognised as Kronn's, so they stay until you remove them.
- Mistral Vibe in Kronn can call Kronn's own tools again (KT-1158). Its
  permission request names only the call id, so Kronn refused every
  `disc_meta`/`disc_list` call, even with full access. Kronn now reads the tool
  from the update that announced the call. A call to another tool is governed
  as before. A refusal now says what was refused, why, and what the user can
  change. `disc_meta`, `disc_list` and `disc_load_other` refuse an unexpected
  argument and point to the right tool (`disc_meta` reads only the current
  discussion).
- An Agent step inside a foreach sub-workflow no longer crashes the backend
  with a stack overflow (KT-1161). In a debug build each nested workflow run
  takes about 1 MiB of stack, so a foreach child filled the 2 MiB default of a
  runtime worker before its Agent step started. The backend and desktop
  runtimes now give their threads 8 MiB, and the static tool catalogue is
  built once instead of on the stack at every Agent step.
- Automation and Planning no longer stall (KT-1148). Automation re-rendered
  in full on every workflow poll (every 3 to 10 seconds), and these renders
  kept the browser busy for close to a second at a time on a loaded machine.
  Both pages now re-render only when their own data changes. Planning starts
  its first load at once instead of waiting 180 ms for typing to settle. While
  a list or a detail loads, both pages show the spinning Kronn logo instead of
  an empty state or the old spinner. The database-size banner on Automation
  now reads the file size only (a few milliseconds) instead of measuring
  every table. That measurement took 1.7 to 2.4 seconds on an 11 GB database,
  and Settings still runs it, but on a connection of its own: it no longer
  holds up the requests queued behind it.


- A workflow's file and line limits could miss a file rewritten at the same
  size in the instant its git index was last written: on Linux the safety
  snapshot copied the index with a new date, so git trusted stale file data.
- `rustls` 0.23.46 fixes RUSTSEC-2026-0346 to 0349.

## [0.15.0] - 2026-10-10

### Upgrade notes

- Workflow Security settings saved before this version now apply as stored
  (KT-1043); nothing is unticked or disabled for you. Check the warning on
  each affected workflow's page. A workflow with "Sandbox (Docker)" ticked has
  every run refused on a native (non-Docker) install, its Cron and Tracker
  runs included. One with "Approval required" now waits for a human before
  every run: a Cron or Tracker trigger leaves runs waiting for approval
  instead of running unattended. One with "Max files" or "Max lines" is
  refused when its project has no directory or is not a git repository. A run
  that had already started when you upgrade is not paused for approval when it
  resumes (after a Gate, a quota wait or a restart), but it is refused by the
  sandbox setting, and newly set limits measure from the point it resumes.
- Cron and Watch triggers saved before this version are pinned to `UTC`
  (KT-1103, migration 242), so they keep firing at the same instants. A new
  trigger without a timezone follows Kronn's timezone instead: to write a
  schedule in local time, clear the `UTC` of an existing trigger and rewrite
  its hours (e.g. `5-19` UTC becomes `7-21` in Europe/Paris, with no rewrite
  at each DST change). `{{time.now}}` without `tz:` now renders in Kronn's
  timezone; add `|tz:UTC` where a template needs UTC.

### Added

- Kronn has a timezone (KT-1103): Settings > Server, an IANA name, by
  default the machine's zone (`TZ`, then the OS setting, then UTC; a Docker
  container reports UTC unless the compose `TZ` is set). Cron and Watch
  triggers without their own `timezone`, and `{{time.now}}` without `tz:`,
  use it. A local time the spring DST change skips does not fire that day;
  one the autumn change repeats fires once. The trigger editor shows the zone
  in use and the next 3 firings in it (`POST /api/workflows/cron-preview`).
  Tracker intervals stay in UTC.
  A run renders its dates in the zone it started under, through Gate,
  quota and restart resumes and in its child runs; a run pinned before this
  version renders in UTC. The desktop app applies the zone at boot too.
- Live Pages: a dataset can now be emptied, re-limited and deleted
  (KT-1104). `clear` is a publish write for every kind (time-series points
  deleted, snapshot set to `null`, collection to `[]`), also usable in a
  `PublishPageData` step without `value_from`. `PATCH
  /api/pages/{id}/datasets/{name}` changes `max_points` / `max_age_days` and
  prunes the existing points at once. `DELETE /api/pages/{id}/datasets/{name}`
  (MCP `page_delete_dataset`) is refused, with the list of references, while a
  workflow writes the dataset, the Page HTML names it or a Page button binds
  to it; a human can force it from the Page, an agent cannot. The data panel
  now shows each dataset's size, last write, writing workflows and whether the
  HTML names it, with Empty, Delete and limit controls behind confirmations.
  Changing limits and reading usage are human-only. The MCP catalogue stays
  within its byte ceiling (lowered to 85,341 B).

- « Ma Todo » ships with Kronn (KT-1030): a board Page (to do / in progress /
  done) over the planning tasks tagged `todo`, with add, edit, ⤒ ▶ ◀ ✓ ↺,
  tags, one discussion per task, and a chevron that shows a card's
  description as rendered Markdown. It is installed once at first launch,
  never again after you delete it, and never over a todo page of your own;
  the Pages sidebar offers to (re)install it. Its six workflows use only the
  new zero-token `TaskBoard` step and `PublishPageData` (no Python, no URL),
  arrive enabled, and no action is pre-approved: approve « todo-move » once in
  the Page's details to save a drag and drop without its card. A drop moves
  the card at once and puts it back with the reason if the launch fails. A
  first-view notice, dismissible, says the page runs without an agent, on
  Kronn's Tasks (Planning) and Workflows (Automation), and that any configured
  agent can adapt it and its workflows through Kronn's MCP tools (blocks, new
  statistics, a validation or sorting step).
  Editing a task writes only what changed, so a long description is never
  cut. Closing the notice is remembered by Kronn for that Page (a sandboxed
  Page has no storage of its own).
  Cards show tags as chips, a priority badge, a relative date, a description
  preview and the linked discussion; a search bar filters the board live by
  title, description or tag, ignoring case and accents.
- An open Page follows a publish at once (`live_page_data_changed` WebSocket
  event) instead of waiting for its 30-second refresh (KT-1030).
- A skill can belong to one project (KT-1128). "Nouveau skill" joins the
  Automation page's create menu, and a custom skill's sheet gets "Modifier";
  the form takes an optional project (the page's project filter by default).
  A project skill is a custom skill of the library carrying
  `metadata.kronn-project`: same file, same `custom-<slug>` id, so the run
  pins and every reference keep working. It is listed on its project's
  AI & automation tab as a project skill ("Dans Kronn seulement" until it is
  published) without becoming a default of its discussions. No other project
  offers it, in its discussions, workflow steps or tab. An agent launch of
  another project that names it is refused by name, before anything starts.
  The tab proposes the user's global skills before Kronn's built-in ones.
  `POST`/`PUT /api/skills` and the MCP `skill_create`/`skill_update` take
  `project_id`: absent keeps it on update, `null` (or `""` through the MCP)
  makes the skill global, and an unknown project is refused. A `skill:<slug>`
  reference resolves the project's own skill and its repository skill in the
  project scope (both at once is refused as ambiguous), then a global custom
  skill, then a built-in; another project's skill never resolves.
- A workflow Agent step loads the project's repository skills (KT-1128). A
  step may name a `repository:<project>:<slug>` skill, which a run used to
  drop without a word. The run pin (KT-1096) reads it as committed on the
  repository's default branch (the remote one when the clone has it) and pins
  it with the other skills: neither the branch the checkout is on, nor the
  run's worktree, nor a later commit changes what the step loads. A skill the
  pin lacks (another project's, not committed on the default branch, no longer
  used by the project, or a launch outside a run) stops the step by name. The
  step editor offers the workflow project's repository skills.

- Repository profile `kronn/project.toml` (KT-920, ADR-005 slice 7).
  Workflows read what is specific to a repository (validation targets, forge
  labels and rules, tracker statuses and transitions, delivery workflows) as
  `{{project.<path>}}` instead of hard-coding it. Kronn reads the file at the
  commit of the main checkout's default-branch ref, never from a run's
  worktree, so a pull request cannot change the rules of its own run, and
  pins what it read with the run tree, one snapshot per project: a resume or
  any child run sees the same values even if the file changes during a
  pause, and the pinned revision fingerprint covers them, as does a trusted
  Page action's approval: changing a profile value invalidates it, and the
  run it admits pins the snapshot that was checked. The schema is typed and
  refuses unknown keys, malformed files and anything that looks like a
  secret, without ever quoting the file in its error; a run that needs a
  missing key fails before its first step, naming it, and projects without a
  profile are unaffected. A completed Full audit drafts a starter profile
  when the repository has none and keeps it with the audit run, without
  writing into the checkout; the audit view shows it read-only with a Copy
  button. Schema and an example:
  `docs/guides/project-profile.md`.
- A workflow step now delegates a plan's subtasks without an orchestrator
  agent (KT-909). `DelegateSubtasks` takes a parent task, launches each ready
  subtask with the worker named by its `worker:<key>` tag, in plan order,
  within the step's concurrency and the plan's blockers, and waits on the
  executions' durable state with no model call. Each delivery is reviewed by
  the step's agent in one fresh, short session that sees the DoD, the diff and
  the worker's report, and answers approve, request changes, reassign or
  escalate. The diff is cut at 40,000 characters: an HTTP reviewer has no
  tool to read the worktree, so past that cut it does not review the whole
  change; Kronn applies the verdict and integrates approved work into the
  run's branch, validations included, through the existing review and
  integration code. The step ends with OK, or with ESCALATED, CONFLICT,
  BLOCKED, FAILED or TIMEOUT for `on_result` to route, and lists per subtask
  its status, integrated SHA, review rounds and cost. The step timeout also
  bounds a review in flight: a verdict that comes back late is kept, never
  applied or integrated past the deadline. Each review is recorded in the run's
  durable LLM-call count before it starts, so a restart keeps the cap, and
  a refused write ends the run before another review. A restarted step picks
  its campaign back up: nothing is launched twice, a verdict already recorded
  is applied without a new review, a reassignment interrupted midway is
  applied once instead of escalating, and boot recovery no longer wakes a paid
  principal turn in the step's room.
- Workflows can use a `Watch` trigger (KT-1099): on its interval, the
  scheduler polls a source with a GET through the API broker (an API
  configured in Kronn, or a saved Quick API; stored credentials, the API's
  default headers, guarded outbound HTTP) and creates a run only when the
  source changed. The stored ETag / Last-Modified go out as a conditional
  request, so a 304 costs nothing; a 200 is then compared by its validators,
  by a fingerprint of the body, or by a fingerprint of a JSONPath result, so
  volatile fields can be ignored. A poll creates no `workflow_runs` row: the
  validators, fingerprint and unchanged/changed/error counters live in
  `workflow_watch_state` (migration 237) and show on the workflow card, and
  three failed polls in a row show the workflow as failing. The first poll of
  a source records a baseline without a run. Each served project's admission
  of a change is recorded with its run, and the baseline advances only once
  every project has its run, so a change detected again (a restart before
  the poll was acknowledged, a project refused by its concurrency limit or
  preflight) runs once per project and never twice. A Quick API the trigger
  polls counts as a dependency: an agent's edit of it disables the workflow,
  it cannot be deleted while named, it travels with the workflow's export,
  and a human edit of it starts a new baseline. The run receives the triggering response as `{{trigger.body}}`
  (credentials removed, cut at 64 KiB) with `{{trigger.status}}`,
  `{{trigger.fingerprint}}`, `{{trigger.etag}}`, `{{trigger.last_modified}}`
  and, in JSONPath mode, `{{trigger.extract}}`. The trigger is editable in the
  wizard and through MCP `workflow_update`; as for any trigger, an agent's
  edit disables an enabled workflow until a human turns it back on, and
  `workflow_create_draft` defaults a Watch to one concurrent run. Watch
  intervals take five cron fields (at most one poll a minute).
- Cron and Watch triggers take an optional IANA `timezone` (KT-1099), so
  "7h–21h Paris" is `0 7-21 * * *` with `Europe/Paris` and follows the DST
  change. Without one the schedule stays in UTC, as before, and the editor
  says so. On the change days, a local time that does not exist (spring) does
  not fire that day, and a repeated one (autumn) fires once, at its first pass.
- Workflow tools now say whether a saved workflow can start (KT-1138). Every
  create, update, import, read and bundle returns a structured readiness
  verdict (`readiness` in the API envelope, `kronn_readiness` first in agent
  tool results, `readiness` in the bundle response): `ready` plus each blocker
  with its workflow, step, rollback flag, kind (validation error, misconfigured
  step, human approval, unsafe interpolation, missing child, cycle, collection
  error), the runtime reason, the action and whether only a human can lift it.
  It covers sub-workflows, triggered workflows and every `on_failure` chain,
  and reads the run's own validators (the Exec allowlist, path and
  irreversible-operation refusals of the main and setup lines now live in
  functions shared by the run and the verdict; a line with a template is left
  to the run). `GET /api/workflows/{id}/readiness` and the new
  `workflow_validate` MCP tool return it on demand; `workflow_list` keeps the
  `misconfigured_step_count` and `unsafe_step_count` it used to drop, zero
  included, and adds `blocker_count` and `human_approval_count` (the misconfigured
  count now includes rollback steps). Saving or enabling is no longer presented
  as readiness, and `ready` means no known refusal, not a guaranteed run. Agents
  still cannot approve a line, pin a script hash or clear its provenance
  (KT-1017): the `workflow-architect` skill and the manuals tell them to fix
  what they can and to list each pending approval to the human. In the
  workflow view, a line that only lacks a human approval is no longer described
  as a dangerous interpolation; it offers "Review and approve", and blocked
  sub-workflows and rollbacks are named. The Exec step schema explains the
  code/data split and the approval agent-written lines need.
- Runs that change nothing are marked, hidden and purged fast (KT-1100). A
  successful top-level run is stored with `outcome: no_op` when every step
  declared or proved that it changed nothing: an Exec step prints a
  `KRONN_NOOP` line, an Agent step puts `"no_change": true` in its envelope,
  and Kronn infers an identical Page publish, a `GET`/`HEAD` call, a Collect
  step made of Quick API reads and the pure data steps. Anything else, a
  worktree, a preserved branch, a dataset write that changed content, a
  recorded non-`GET` call or any row pointing at the run, keeps it `changed`.
  The outcome is returned by the run API and by MCP `workflow_runs` /
  `workflow_run_get`. The run list hides these runs by default
  (`hide_no_op=true` on `GET /api/workflows/{id}/runs` and `/runs/count`),
  offers "Show runs without changes (N)", and folds a streak of them into one
  greyed row, "N runs without changes since HH:MM". A no-op run keeps the
  first 2 000 characters of each step output and is deleted 24 h after it
  finished by the existing run retention pass. A workflow can carry its own
  retention (`retention: {no_op_hours, success_days, failure_days}`, editor
  card "Run retention", `workflow_update`), which overrides the global
  windows per class; `0` keeps forever. Runs that own a worktree or that
  something references are never deleted. Retention is human-only on a
  stored workflow: an agent's `workflow_update` refuses a changed
  `retention`, and a `kronn/` re-import keeps the stored one.
- Multi-agent discussions now say who is launched and how (KT-1109). While a
  draft names several agents, the composer shows "N agents launched in
  parallel" with one chip per agent, and the sent message's routing line adds
  "N in parallel". When the first named agent is told to delegate to the
  others ("@opencode lance @codex et @claude", "ask", "demande à", "fais
  juger par"…), sending first asks whether that agent should orchestrate
  them. Orchestrating attaches the other agents to the discussion
  (`attach_agents` on `PATCH /api/discussions/{id}`, which never dispatches
  them and is refused to agents' bridge tokens) and sends the message to the first agent alone, which hands off with
  the existing `kronn:handoff` marker; the routing line then reads
  "orchestrates @codex, @claude". The choice is offered only when agent
  collaboration is on, warns when the paid handoff limit is below the number
  of delegated agents, and launching everyone in parallel stays one click
  away. The answer is bound to its discussion and draft: editing the text,
  switching discussion or cancelling while the attachment is pending sends
  nothing, and only the first choice taken runs. A native reply that names an agent in prose without launching it now
  carries a discreet note saying so and why: already scheduled on this turn,
  or a plain mention with no handoff.
- The orchestrate-or-parallel choice is also offered when the first named
  agent is given a role or job over the others (KT-1152): "@opencode tu es
  juge des blagues de @codex et @litellm", "compare les réponses de",
  "arbitre entre", "you are the judge of", "eres el juez de", "评判"… The role
  must reach the next agent within one clause, so a plain list, a parallel
  ask ("@a et @b donnez votre avis") or "@a compare X, @b aussi" still
  launches everyone in parallel without asking.
- The configuration assistants (custom API plugin, workflow and Quick API
  ApiCall step) keep their conversation when closed instead of deleting it
  (KT-1111). Each one is created and filed in one transaction as an assistant
  conversation linked to the plugin or the step it configures (migration 226,
  `assistant_conversations`; `POST /api/discussions` takes an `assistant`
  context). It shows in a new Assistants section of the discussion list rather
  than in Recent or the project tree, and the global search finds it. The plugin
  form and the ApiCall step list their "Assistant conversations" (date, agent,
  whether the last proposal was applied); "Resume" reopens the same
  conversation in the assistant, and deleting one is an explicit, confirmed
  action. Conversations started before a plugin, workflow or Quick API is saved
  are attached to it on save, and stay attachable after a failed save: the
  browser records them as owed to the saved plugin or step (by conversation id,
  never by name), which lists them and attaches one on resume. Without browser
  storage (disabled or cleared), such a conversation stays reachable from the
  Assistants section but is no longer reattached to its plugin or step on its
  own. A saved ApiCall step is known by its durable id, so renaming it keeps its
  conversations. Every write into an assistant conversation (its creation, user
  messages, agent replies and streamed checkpoints, edit-resend and revisions)
  masks the stored credentials of the linked plugin, common token shapes, and
  the form values the assistant names with a message: those are held in memory
  only, per dispatch, until that dispatch's reply is written or the dispatch is
  over (a queued or running one keeps them however long it lasts). The assistant
  masks them on display too. A custom API's literal header values
  reach the agent only for known non-sensitive headers or `${ENV.*}` references
  (KT-1041). The logical export (v7) carries the links; older archives still
  import. The new routes (`GET /api/assistant-conversations`,
  `PATCH /api/assistant-conversations/{discussion_id}`) are not open to agents'
  bridge tokens. Conversations deleted by earlier versions cannot be recovered.
- A plugin's API can now be limited to chosen agents (optionally one model
  each), to local models only, or blocked, as a whole and per endpoint, from
  the plugin's Access tab (KT-1026). Under a policy the broker is strict: only
  declared endpoints (the spec's and the policy's) can be called, with exact
  method and segment matching (a placeholder keeps the literal text around it,
  so `report-{id}.json` never opens `private.csv`; malformed braces are
  refused), so `/blocks/{id}/children` no longer reaches what a rule on
  `/pages/{id}` protects; an encoded slash, a dot segment or a path leaving the
  base URL is refused. Every request the call sends is re-decided: each next
  page and each redirect hop, including one that turns a POST into a GET. The
  decision uses the identity Kronn issued at launch (the bridge token carries
  the agent and its model; a native agent's tools are told the model the
  runner actually sends), never the request; a call without a token is an
  agent with no identity. "Local only" means an Ollama model on a loopback host
  that is not a cloud model, in a discussion whose agents are all local (a
  joined CLI counts as remote), or a workflow whose Agent steps all are,
  rollback steps included, read from the definition the run pinned (a run
  with no readable pin counts as an unknown, hence remote, audience; so does
  any step type that may hand data to an agent Kronn cannot name, including
  ones added later). Requests and rules are compared in one canonical form:
  an encoded unreserved character is decoded (`/users/%6De` is `/users/me`),
  other escapes take uppercase hex, an ASCII character the URL escapes in a
  path (space, quote, angle brackets, backtick, braces) is escaped, and a
  non-ASCII character (a Unicode space included, never trimmed) is its UTF-8
  escapes, as the request URL is sent. A rule holding a control character
  (which the URL drops or escapes) is refused, and a stored one makes the
  plugin refuse every call until it is fixed. A rule is stored in that form, and two rules that reach the same
  form are refused. Workflow ApiCall, BatchApiCall and
  CollectApiData steps, Quick API runs and the MCP `api_call` all pass the same
  gate, before any token exchange or outbound request; a refusal names the
  rule and no secret. The API context, `mcp_list` and `api_endpoints` show each
  plugin's and endpoint's rule. Only a person sets or widens a policy
  (`PUT /api/mcps/servers/{id}/access-policy` is not open to bridge tokens). A
  plugin without a policy behaves as before. The `api_call` description no
  longer claims undeclared paths are refused when they are not. Database
  exports move to v7 and carry the policies; an older export keeps the local
  ones. On a hybrid plugin the panel says its MCP connection keeps its own
  access, outside this restriction.

- A Live Page action can run without its validation card once a human has
  approved it from the Page's details (KT-1029), for example moving a card
  between columns of a Todo page. Only workflow actions whose steps are all of
  a type known to run no agent (any other or future type is refused), without skills,
  profiles, directives or Quick Exec data sources, without a typed or
  project-environment value and in the Page's own project are eligible; the
  panel says why the others are not. The approval is bound to a fingerprint of
  the action block, the Page's project and the workflow's shared revision
  identity, and falls for good, with a visible notice, as soon as the block
  changes or leaves the Page, or the moment its workflow or one of its Quick
  APIs is written, disabled or deleted, even if the change is undone. Each
  trusted launch needs a proven live click, runs only under the approval it
  was claimed with, and its run is admitted, created and pinned to the
  approved definition in one transaction, so a later edit or revocation never
  makes it execute anything else; it is rate-limited per row and per action
  and appears in the button's history. Withdrawing an approval is one click and applies to the next click.
  Neither the Page nor an agent can approve: the routes are outside the
  bridge-token list and refuse an agent identity; `page_get` shows agents which
  actions are trusted.
- The waiting reply bubble now shows what the agent is doing from the first
  message on, for every agent (KT-1108). Before the first word it lists the
  real startup phases with their times: a native ACP runtime being launched,
  `initialize`, `session/new` while the project's MCP servers start (with
  their count), model selection, a Claude or Codex CLI starting with its own
  MCP servers, then the prompt sent and the wait for the model. Once the model
  works, the phase follows it (thinking, using a tool, writing) and an
  expandable list shows its tool calls by category, as in the audit Details
  panel. When the agent is silent, the bubble counts down the inactivity
  delay in force (a startup phase's own bound, the model's delay, or a tool's
  wider one), and a stop by Kronn is announced in the same bubble. Every phase
  comes from the code path that observed it; frames carry categories, counts
  and durations only, never a tool's name, a path, a URL or agent text. They
  travel on the local WebSocket only, coalesced per run (a phase or tool change
  at most every 0.5 s, a mere sign of life at most every 5 s), and a frame
  older than the one shown, or one after the run stopped, is ignored. A page
  opened or reconnected mid-run reads the runs in progress from
  `GET /api/discussions/{id}/run-progress` (not open to agents' bridge
  tokens); reading it never restarts a run's silence. A reply always shows its
  latest attempt, and a run is shown only in the bubble of its own dispatch or
  turn. HTTP agents (Ollama, LiteLLM, OpenAI-compatible) report each request
  with its first-token deadline (each retry attempt with its own, no countdown
  during the backoff), the wait for the model's next answer after a tool,
  reasoning and keepalive chunks as activity, a Kronn tool from the moment it
  starts, and their own inactivity stop. A streamed HTTP run now owns its
  silence bounds the way an ACP run does (first token, then a fresh bound once
  the headers come, then each chunk), so the discussion and workflow
  consumers no longer arm a second text-only timer on it; the global deadline
  still holds.
- HTTP agents (Ollama, LiteLLM, NVIDIA, custom connections) can find, create
  and update Pages: `page_list`, `page_get`, `page_create`, `page_update_html`
  and `page_add_dataset` join their native catalogue and call the same handlers
  as the MCP bridge, within the same scope: project-less Pages and the current
  project's Pages, never another project's. A discussion agent gets them in
  the full catalogue (or in the `pages` family with `KRONN_TIERED_TOOLS=1`); a
  workflow Agent step gets them too, while Planning mutations stay out of its
  catalogue. Asked to find a Page, create one and draft the workflow that
  feeds it, a local `qwen3.8:27b` did all three; the workflow published to the
  new Page on its first run.
- Before a multi-agent discussion or room starts, Kronn checks every selected
  agent in parallel without calling any model (KT-1107): installed, full access
  on for the native ACP agents, signed in where the CLI has a status command
  (`claude auth status`, `codex login status`, `kiro-cli whoami`), and, for the
  native ACP agents, a real `initialize` + `session/new` with no prompt, in the
  project directory, so a project MCP server that blocks the start is caught
  within the launch's own 90 s bound. The form shows ✅/❌/❔ with the reason;
  if an agent is not ready, nothing starts until you fix it, remove it, or
  launch anyway, and the room shows the same state in a compact notice. Results
  are cached for 3 minutes per agent and project, and dropped when the agent's
  settings, full access, key or the project's MCP servers change. A check that
  cannot be made without the model (OpenCode, Gemini, Copilot and Vibe sign-in)
  is reported unknown, never ready. `POST /api/agents/readiness` is not open to
  a launched agent's bridge token.
- Settings → Agents shows the command Kronn actually runs for each agent (the
  installed CLI's path, or the `npx` command it falls back to) and the version
  that command reports, with a warning when it is the `npx` fallback. A run
  that falls back to `npx` records the command and version in its provenance,
  never in the agent's output.
- A Live Page's slug can be renamed: from the slug pill in the Page header
  (with a preview of the new link), through `PATCH /api/pages/{id}` with
  `slug`, or by an agent with `page_update_html({page_id, slug})` (MCP or
  native tools). The new slug
  follows the creation rules (lowercase ASCII, unique across all projects). The
  former slug keeps opening the Page, so links, workflow steps and API calls
  that name it still work, and it stays reserved for that Page until the Page is
  deleted: no other Page, import included, can take over its old links.
  Exports name the Page by id where a workflow still used an old slug, so the
  bundle imports cleanly (KT-1098).

### Changed

- "Invite" in a discussion now copies the `kr-join-…` token alone as soon as
  it is minted, and the modal says so (KT-1146). Three tabs replace the "Full
  handoff" checkbox: Token only (default), Simple instruction and Detailed
  instruction, each with its own copy button. When the browser refuses the
  clipboard, the modal says the copy failed and leaves the text selected for a
  manual copy. The token itself is unchanged. The copy is started inside the
  click, so the desktop app's WebKit view accepts it even when minting is slow.
- A task description in Planning, and in the discussion plan panel, now shows
  as rendered Markdown by default, with the same safe renderer as discussion
  messages (no raw HTML, safe links), instead of raw source (KT-1137). A "Raw"
  switch at the top of the block shows the source, where it can be edited in
  Planning; the chosen mode is remembered in the browser. Switching never
  drops an unsaved edit, and saving is unchanged.
- `{{previous_step.data…}}` and `{{steps.<name>.data…}}` no longer read an
  older step's data after a step that produced no envelope (KT-1105). To read
  an earlier producer, name it with `steps.<producer>.data`, which works as
  long as that producer has not been re-run without an envelope.
- The "Run without confirmation" panel of a Page is now one collapsed summary
  line, for example "4 to approve · 1 approved · 22 not eligible" (KT-1141).
  "Details" opens the list: eligible actions first, with their approve and
  withdraw controls, then the others grouped by reason, each group collapsed
  with its count. The browser remembers whether you left it open. Approving
  and withdrawing work as before.
- Kronn's built-in skills are now listed apart from the user's (KT-1140). On
  the Automation page's Skills cards and on a project's AI & automation tab,
  the skills in use split into "Skills Kronn" and "Mes skills" (custom,
  project and repository skills). Both screens use one shared rule and the
  same badges: "Projet", "Dépôt" and "Pas synchro". "Pas synchro" shows only
  on the project tab, the one screen that knows the sync state. Unused skills
  stay folded below, as before.
- A pull request page now lists only real CI results (KT-1159). Adding a
  label or editing the description no longer starts CI Tests and CI Build,
  whose skipped jobs used to hide the results of the run that really tested
  the change. Those events start CI Verdict instead, which publishes only
  `ci-quality-gates` and `ci-build-gates`, repeating the verdict of the run
  for the same head and base. A label added after a push re-runs that run, so
  its real results appear. CONTRIBUTING explains which checks to read.

### Fixed

- In a multi-agent room, a native agent that posts through MCP during its own
  run no longer starts agents a second time, cancels itself or runs out of turn
  (KT-1151). Its post now belongs to its turn: it replies to the message that
  started the turn and carries the turn's job. Agents already scheduled on the
  same human message are not started again. The bridge sends the turn it was
  launched for, and Kronn checks it against the running job. After a restart,
  the agent's own post no longer cancels its turn, but a newer human message
  still does. Turns in one discussion now run in the order of their messages
  (then creation). Before, whichever turn won a race ran first.
- Under Vibe, Kronn's bridge now knows its discussion: `disc_meta` no longer
  fails with "no disc bound" (KT-1082). Vibe starts MCP servers without its own
  environment, so Kronn declares the discussion id and backend URL for the
  bridge in `session/new`. The bridge token and a workflow step's capability
  are not declared, because Vibe saves that declaration in its session files:
  they go in an owner-only file (mode 0600, in a 0700 directory) that the
  declaration names and that is deleted when the session ends. The other
  native agents are unchanged.
- A workflow Agent step on OpenCode, Vibe, GitHub Copilot, Gemini CLI or Kiro
  whose full access is off now fails at once with
  `native_full_access_required` and a message naming the agent and the fix
  (KT-1086). It is not retried, not read as a quota limit, and the
  `on_failure` chain still runs. The workflow's readiness check lists it as a
  blocker before the run, and the step's agent picker badges such agents
  "Needs full access" until the setting is turned on.
- Kronn's action card names the row in words a Page gives it (KT-1030). A
  Page can add `data-kronn-binding-labels` next to `data-kronn-bindings`; the
  card shows those labels instead of raw selectors such as
  `__col_in_progress__`, with the selectors still in the tooltip and in the
  resolved values. Labels are display only: they never reach the server, and
  the action block and its trust approvals are unchanged. « Ma Todo » labels
  its moves in four languages (task title, position, column). In a narrow
  column the card now puts its title on its own line and wraps the row
  instead of cutting it.
- `agent_list` no longer reports a named HTTP connection as reachable just
  because an address is saved (KT-697). Each connection now gets a short
  network probe (an unauthenticated `GET /v1/models`, 3 s max, all connections
  in parallel, results cached 30 s). A connection whose host does not resolve,
  refuses, times out, fails TLS or answers 5xx is `reachable: false`, not
  `available`, with the `endpoint_unreachable` code the legacy LiteLLM family
  already used. A new `connectivity` field tells `verified`, `unreachable`
  (with `unreachable_reason`: `dns`, `refused`, `timeout`, `tls`,
  `http_status`, ...) and `unverified` (no probe, or a result older than 60 s)
  apart. The connection stays listed with its id and media slots either way.
  The field never contains the address or a credential.
- The desktop app no longer blocks allowed third-party players in Live Pages
  by its own headers (KT-1123). It sent
  `Cross-Origin-Embedder-Policy: require-corp` and
  `Cross-Origin-Opener-Policy: same-origin` on every response, so the webview
  refused any frame whose site does not opt in to that policy, whatever the
  allow-list said. Both headers are gone, as in Docker, which never sent
  them; the frame policy (`frame-src`) is unchanged. They only made
  `SharedArrayBuffer` available, so the local voice workers now run on a
  single WASM thread in the desktop app, as in Docker. Proven: in a native
  macOS WKWebView, a synthetic player without COEP/CORP is blocked under the
  old headers and loads without them, and text-to-speech still produces
  audio without isolation. Not yet checked: the packaged app, a real player
  (YouTube…), Windows WebView2. Local speech-to-text currently fails in
  WKWebView with or without these headers (KT-1143), unrelated to this change.

- The `kronn-internal` MCP bridge no longer hangs on a large request
  (KT-1139). A `workflow_update` of more than 256 KiB, such as a 1 MiB
  workflow, killed the bridge's input reader, and every later call waited
  forever. A request may now carry up to 8 MiB. A larger one is refused with a
  `request_too_large` error that runs nothing, and the connection keeps
  working. If the reader stops on an unexpected error, the process exits
  instead of hanging. The limits are listed by `bridge_info` and in
  `initialize`. Large workflows are now edited one step at a time:
  `workflow_get` takes `step_name` or `step_index` to read one step, and the
  new `workflow_update_step` changes one step through the same save as
  `workflow_update`, with every authorship, approval and enable rule unchanged.
  If another save changed the workflow after the edit read it, the edit is
  refused with a conflict and nothing is written.
  See `docs/operations/mcp-servers/kronn-internal.md`, "Transport size
  contract".

- The header of a Page in the Artifacts view no longer puts the Page id under
  the "Open in new tab" button (KT-1141). The id stays with the title and
  ends in an ellipsis when space runs out; the buttons share one height and
  wrap onto their own row when the view is narrow, and below 480 px "Open in
  new tab" shows only its icon.

- A CLI listening in a room with `disc_wait_for_peer` no longer goes deaf
  between polls (KT-703). The bridge slept the server's pacing delay after
  each quiet 15-second poll: 40 s while a human was active, up to 480 s in a
  quiet room. A message posted during that sleep waited for the next poll,
  so after a calm period a reply could take more than 8 minutes while the
  session still looked present. The bridge now starts the next poll right
  away, so it listens continuously and a new message arrives within one poll.
  The server's pacing values are unchanged. A CLI reading a wake result is
  also no longer told to rotate its session in a shared room. The
  `session_budget` verdict now appears only in a delegated task's execution
  room, where rotating is the intended response. In a shared room, it had
  been read as an order to stop listening.
- On macOS, the `caffeinate` that keeps the Mac awake during a run no longer
  outlives Kronn (KT-1127). It is now started as `caffeinate -i -w <backend
  pid>`, so macOS ends it when the backend exits, however it exits (crash,
  SIGKILL, update, desktop quit, normal exit while a run holds the lease).
  Before, an orphaned `caffeinate` stayed alive forever and the Mac never
  idle-slept again. It also no longer inherits descriptors the backend itself
  inherited without close-on-exec.
- The workflow Security settings now take effect (KT-1043). They were saved
  but nothing read them. "Sandbox (Docker)" refuses to start a run outside a
  container. "Approval required" pauses each run before its worktree, its
  hooks and its first step, until a human approves or rejects it from Kronn;
  an agent's bridge token cannot decide it, and a sub-workflow child with this
  setting fails instead, since it cannot pause. "Max files" and "Max lines"
  compare the run's working tree, by content, with its state before the
  workflow's hooks ran (changes already there are not the run's; hook changes
  are). The run is measured after each step, its declared artifacts included,
  and once more after the `after_run` hook. Files git is told to ignore changes
  to (assume-unchanged, skip-worktree) are still measured, deletions
  included; a flagged path already missing when the run started (a sparse
  checkout) is not counted. The step that goes
  past a limit fails and the run stops there for good: no quota wait, no
  `on_failure` chain, changes kept for review. Such a stop in a sub-workflow
  ends its parent the same way, and a foreach dispatches no further item. A
  step's `---STATE:` output can no longer write Kronn's own `__kronn.*` run
  state, and a recorded starting state that cannot be read stops the run
  instead of being taken again. These limits need a git working tree; without one, or without a
  project directory, the run is refused. The editor and the workflow page say
  which stored settings this host would refuse. An agent that loosens these
  settings on an enabled workflow disables it, as for any other execution
  change.
- Unticking every Security option in the workflow assistant is now saved
  (KT-1044). An all-cleared panel used to send no settings, so the old ones
  came back on reload.
- The reply bubble's Logs panel follows the same no-leak rule as the run
  progress (KT-1120). A tool call is logged by its category only (`→ Read`
  when it starts, `✓ Read` when it ends), never by its name, input, file path,
  URL or command. Kronn reduces the line before sending it, so the client never
  receives those details. When the agent writes to its stderr, the panel shows
  a fixed `⚠ Diagnostic` notice once, never the text: it can name paths and
  URLs. A failed run's error message still carries that text, and the tool
  calls recorded in the transcript are unchanged.
- A `PublishPageData` write fed by an `Exec` stdout no longer publishes a
  truncated document (KT-1105). An `Exec` step a publish reads now keeps up to
  2 MiB of output instead of 100 KB; a stdout cut at that limit, or one that
  looks like JSON but does not parse, fails the publish step with an explicit
  message and the Page keeps its previous data. A JSON stdout is stored as the
  JSON value rather than a string (pages that `JSON.parse` a string keep
  working). The Exec envelope now flags `stdout_truncated` and
  `stderr_truncated`, and Exec output is scrubbed of the project's GitHub
  token by value.
- `Exec` and `ApiCall` no longer read a whole command output or JSON response
  into memory before cutting it (KT-1047). An Exec step drains stdout and
  stderr as they come, keeps its limit plus a small margin and counts the
  rest, so a command writing tens of MB stays bounded and its output ends with
  the truncation marker. An ApiCall JSON response is read chunk by chunk and a
  body past 16 MiB fails the step with an explicit message instead of being
  loaded; an error body is read only as far as its excerpt needs.

- A workflow run now executes only the revision it started with (KT-1096).
  Its first execution freezes the workflow and every Quick Prompt, Quick API,
  sub-workflow, skill, directive and profile it can load, sub-workflows' own
  dependencies included (migration 227). Gate and quota resumes, restarts,
  rollback steps, sub-workflow children and BatchQuickPrompt fan-out (chain
  prompts included) read that revision, so an agent's edit made while a run
  is in flight never reaches it. That covers the skills, directives and
  profiles a fan-out child starts with (a pinned run gets them inline in its
  prompt, never through the shared native skill and profile files a project
  sync can rewrite mid-turn),
  the `ref:` targets and `{{ref:…}}` values the run resolved, and a rollback
  Agent step's Quick Prompt. The edit still disables the workflow for the
  next runs, and a resume of such a run still needs a human. A dependency
  deleted during the run fails its step with a message naming it. A run whose
  workflow an agent's change disabled between its launch and its start does
  not start. A run started before this version is pinned at its next
  execution.
- A workflow run that ends partial, fails, is cancelled or is stopped by a
  guard no longer deletes a worktree holding uncommitted or untracked work,
  in itself or in a worktree created inside it, and neither does the boot
  purge of finished runs (KT-1096). The worktrees and their branches stay,
  the run's last step says where, and a later start removes them once clean.
  A clean set is removed without `--force` and never forced: when git
  refuses (work written after the check, a submodule), it is kept with the
  reason. A fully successful run still discards what it left
  uncommitted, as before: once every step finished, those files are its own
  scratch.
- A Gate with `gate_checkpoint_before` no longer commits the operator's
  checkout (KT-1042). The checkpoint commit is taken only in the run's own
  worktree (workspace isolation). In shared mode no checkpoint is taken and
  the Gate message says so, where Kronn used to `git add -A` the project
  directory and commit the operator's unstaged and untracked work on their
  branch. The checkpoint records the work under review: "Request changes"
  re-runs the target on top of it, after checking that the worktree is still
  exactly that commit. It no longer runs `git reset --hard`, which could only
  discard commits made by someone other than the paused run.
- The anti-loop guards of a workflow run no longer restart from zero at
  every resume (KT-1046). Step visits (`loop_detection_max_revisits`, and
  `{{iter.<step>}}`), the fires of each capped Goto edge, the total iteration
  safeguard and the `max_llm_calls` count are stored on the run. They carry
  over a Gate decision (approve or request changes), a quota wake-up and a
  restart, so a loop through an auto-approved Gate stops where the same loop
  without a Gate stops. A step replayed after a quota refusal or a crash is
  not counted as a new visit, and a call refused for quota does not count
  against `max_llm_calls`. The LLM calls of sub-workflow children are recorded
  on the root run as they happen (a new `tree_llm_calls` column that only
  grows), so a crash in the middle of a foreach no longer forgets what the
  children that already finished spent. If that count cannot be written or
  read, the run stops before its next call instead of continuing on a total
  it cannot trust, and a child whose calls were not recorded is not taken as
  done. That stop is terminal: the run ends Failed without its `on_failure`
  compensation steps, quota wait or recovery rule, since each of those could
  spend further calls. This covers calls that finished: a child still running when the
  backend dies is started again on resume, so its interrupted call can be
  billed twice. Runs paused before this version
  rebuild their counters from their own step history and that of their
  sub-workflow children.
- `agent_list`, `task_exec_prepare`, `task_exec_launch` and the tier pickers
  now read the launch preflight's own catalogue decision (KT-860). A tier the
  preflight would refuse is listed with the reason (`model_unavailable`) and
  is not launchable; a tier whose model disappeared shows the model that runs
  in its place. A run refused at preflight keeps the refused model, the reason
  and the next action in `last_error`, instead of "model catalogue preflight
  refused this model".
- Codex: Kronn now reads the whole `model/list` of the installed CLI, every
  page and hidden models included (KT-667). A tier model absent from that
  complete listing keeps its tier and launches with a warning shown
  beforehand: a "Not listed" label on the tier choice (detail on hover), a
  `model_not_listed` warning in `agent_list` and `task_exec_prepare`, and a
  notice at the head of the run. It is never refused or replaced on that
  evidence, since a listing does not prove account access; only a refusal the
  provider actually returned blocks a model. A listing cut short or a failed
  refresh leaves the evidence unknown, and models an older visible-only
  listing had marked disappeared are available again. When Codex answers
  "requires a newer version of Codex", the error names the model and the
  detected and latest known CLI versions instead of repeating "upgrade".
- A workflow Agent step refused for a provider quota or session limit no longer
  fails the run and loses the steps before it (KT-811). The run waits in a new
  `WaitingQuota` status, shown as "Quota" rather than as a failure, and resumes
  at the refused step after the announced reset (Claude's
  `resets 9:40pm (Europe/Paris)`, a `Retry-After`, Codex's `try again in …`,
  an ISO reset), even across a backend restart. Without a usable reset, past
  the workflow's time limit or after repeated refusals, the run is parked
  with its reason and a "Resume" button; it can be cancelled while it waits.
  Automatic resume covers linear root runs only: a sub-workflow child, a batch
  or an `on_failure` compensation is not resumed, and a child ends `Failed`
  with its step marked as quota.
- On a native macOS backend, an agent CLI installed under a `KRONN_HOST_BIN`
  directory is run instead of being skipped for `npx`: the Darwin host-binary
  guard now applies only inside a container. In a container, resolution goes
  on through the `PATH` after a skipped host copy, so a later Linux copy wins.
- A workflow step with a `TypedSchema` output on an OpenAI-compatible provider
  sends `strict: false`. The envelope schema does not satisfy OpenAI's strict
  subset (every object `additionalProperties: false`, every property
  required), so a provider enforcing it refused the request.
- A reasoning-effort override that differs from the catalogue only by case
  (`High` for `high`) is still refused, and the refusal now names the
  catalogue's spelling. When the catalogue entry itself carries stray
  whitespace, the refusal says so instead of asking to retype the value.
- The custom API helper and the workflow API call helper now show an Apply
  card when the agent wraps the `KRONN:APPLY` marker in its own code fence,
  puts it on the first line inside the json fence, or writes it in another
  letter case, with LF or CRLF line endings (KT-1110). Both helpers share one
  parser, which only accepts a JSON object and ignores the word used inside a
  sentence. When a proposal block cannot be read, even next to a valid one,
  the chat shows a notice with a Retry action and the raw JSON instead of
  dropping it silently. The system prompts in the 4 locales now say that the
  marker is a bare text line outside any code fence.
- Live Pages: the browser itself now refuses to frame a site that is not
  allowed, in desktop, native and Docker alike (KT-1115). Every app document
  carries `frame-src` and `child-src` set to the app plus exactly the allowed
  sites (same rules as the Docker gateway: no wildcard, an `http://` site only
  with its `https://` counterpart, 16 KiB bound). An allowed site that
  redirects, or navigates its player, to another origin is blocked before any
  request reaches that origin. The desktop sets it on the documents its
  embedded backend serves (its Tauri CSP is null), native mode through a Vite
  plugin reading the same backend route, Docker through the gateway as
  before. Removing an allowed site now takes its players down at once in every
  open tab, a visible one included, through a local WebSocket event that
  carries no origin; a tab that was disconnected re-reads the list when it
  reconnects. A site allowed after a page was opened shows a "Reload to show
  this content" notice until that page is reloaded, since a document keeps the
  policy it was served with. Because that policy still admits a revoked site,
  revoking any site takes down every player of the open tabs, not only the
  revoked site's (an allowed player may have been redirected to it), until the
  page is reloaded; a change also hides the players until the new list is
  read, so a failed read never leaves them running. Each document carries the
  exact sources its policy was served with (`<meta
  name="kronn-served-frame-src">`, written with the header in every mode), and
  the page compares later lists against it, so a site revoked before a Page is
  first opened is still treated as revoked; without that marker no player is
  drawn. An allowed `http://` site whose `https://` address is not allowed is
  never framed; its placeholder now says to allow it over https instead of
  asking for a reload. This lifts the two 0.14.3 known limitations on
  embeds, except the one below.

### Known limitations

- An allowed embed site can itself embed other origins inside its own page:
  Kronn controls only what the Page frames directly, redirects included
  (KT-1115).

## [0.14.3] - 2026-10-07

### Upgrade notes

- OpenCode, Vibe and GitHub Copilot now start only with full access. To keep
  using them, tick their full-access box in Config › Agents › <agent> (the same
  applies to Gemini CLI and Kiro).
- P2P federation is now off by default and the upgrade never turns it on. If
  you use it, enable "Accept P2P connections" in Settings › Identity, and add
  the LAN or Tailscale names you open Kronn from to the frontend origins list
  there, or their WebSocket is refused.
- Imported workflows (`.kronn-workflow.json`, bundles, agent proposals) and
  workflows an agent creates or changes arrive disabled. Review them in the
  Automations banner and re-enable the ones you trust.
- Migrations 220 to 223 run at startup and take about a second (220 to 222
  measured at 1.2 s on an 11.5 GB database).
- Secrets leave `config.toml` on the first start and move to the encrypted
  database (see Changed and `docs/operations/key-management.md`).

### Known limitations

- OpenCode, Vibe, GitHub Copilot, Gemini CLI and Kiro require full access: no
  restricted mode can be promised for them. A per-launch isolated runner is
  planned for 0.15.
- Inbound P2P is off by default. While it is on, a peer is identified only by
  its invite code, so anyone who knows an accepted contact's code and can reach
  the port could impersonate it; pairing secrets are planned for 0.15.
- A workflow run already in flight can still load a Quick API or Prompt that
  an agent changed during that run; revision pinning is planned (KT-1096).
- A SubWorkflow foreach read can still be fooled by a process that mutates the
  worktree while it runs (KT-1055).
- With localhost trust on (auth off, or `auth_strict_localhost = false`), a
  local process without a bridge token is indistinguishable from the user;
  strict localhost is the mitigation until positive human authorization
  (KT-1034).
- P2P mirrors joined before 0.14.3 have no recorded host: join them again with
  their code to resume syncing.
- Agent text and tool metadata in transcripts, audit streams and run records
  stay visible to the project's humans and to agents holding a bridge token
  for that project; per-agent filtering is planned for 0.15.
- On Windows, stopping a native ACP agent ends the agent's own process but not
  the MCP servers or tools it started; owning the whole tree (a Job Object) is
  planned for 0.15.
- An allowed embed site can redirect to or frame another origin: Kronn checks
  only the embed URL's starting origin; browser-enforced frame-src across
  redirects is planned for 0.15 (KT-1115).
- Removing an allowed site reaches an open tab at its next focus (KT-1115).

### Added

- Live Pages can show third-party players (a Suno or video embed, for example): a Page writes
  `<div data-kronn-embed="https://player.example.com/embed/x">` and Kronn draws the player itself,
  only for sites listed under Configuration > Artifacts > External content. The Page's sandbox and
  CSP are unchanged. Artifact bundles declare the sites they embed, and the import asks before
  adding any. Adding a site, directly or during an import, needs local access or the API token even
  when authentication is off. Works in Docker too: the gateway lists exactly the allowed sites in
  the app's `frame-src` (an `http://` site only when its `https://` counterpart is allowed too), and
  a newly allowed site plays after a page reload. Contributed by Barbé Rémi.

- When a dangerous Exec step needs a manual fix, its warning now offers a ready prompt to copy and hand to an agent: it names the workflow, each step and value to fix, and the safe pattern (KT-1017).

- Each project now decides whether its agents receive a GitHub token (KT-1006,
  D2). A "GitHub" row on the project's Overview, and a chip in the discussion
  header, show the state: not connected, available but off, connected through
  your gh login, or connected through a token pasted for the project and
  stored encrypted. "Connect GitHub" asks for confirmation, shows the scope
  GitHub reports for the token (OAuth scopes, or the repositories of a
  fine-grained token, or why it could not be verified) and recommends a
  fine-grained token when the gh token reaches every repository. Turning it
  off applies to new launches; agents already running keep what they received.
- A running audit step now has a "Details" toggle listing the agent's latest
  actions, newest first: each one only as a fixed category (read, search,
  edit, command, web, MCP tool, Kronn, plan, other) with its age. Tool names,
  paths, commands, URLs and titles are never shown or kept, whatever the agent
  or runtime. The same categories replace the tool name and target in the live
  tool chip, the audit stream's events, an HTTP agent's label and a workflow
  step's live activity; an activity a workflow run stored before keeps its
  count only when read, in workflow runs and their shared-run projections.
  Agents' bridge tokens get the counts, not the list. Logs and traces of a
  tool call the model made up name its category, never the name it chose. The
  open state is remembered per project for the session. Known limitation: the
  boundary is the project. Agent text and tool metadata in transcripts, audit
  streams and run records stay visible to the project's humans and to agents
  holding a bridge token for that project; filtering them toward other agents
  of the same project is planned for 0.15.
- The Agents page shows a one-time notice listing the agents that really run
  with full access, whether by setting or forced in Docker, with the risks and a
  link to the switches (KT-975).
- Under Docker, Codex always runs without its own sandbox, which cannot start
  inside the container; the full-access switch now shows that state as locked
  and always on instead of "Restricted" (KT-975).

- Run retention (KT-984) is opt-in on every install, new ones included:
  nothing is emptied until you choose a window in Settings → Database, where
  30 days is the suggested value. While it is off, the Automations page and
  Settings → Database show a banner with the database size and a button to the
  setting; it can be dismissed and comes back past 2 GB. Once on, every 6 hours, in chunks that
  leave the database free between them, Kronn empties the step outputs of
  workflow runs finished before that delay and keeps the runs, their
  steps, statuses, timings and tokens. Runs in progress, paused or
  interrupted, batch and compare runs, runs that still own a worktree and runs
  referenced elsewhere (child runs, discussions, ratings, live pages,
  questions, room activity) are never touched. A run's worktree path is
  cleared as soon as the worktree is removed, so a finished run does not wait
  for a restart to become eligible. The delay is set in
  Settings → Database ("keep forever" turns it off). Settings → Database also
  offers to compact the database, which gives the freed space back to the
  disk and reports the size before and after; on a generated 4 GB base the
  file went from 4.1 GB to 833 MB. The opt-in deletion of old runs
  (`run_retention_days`) now follows the same rules in chunks instead of one
  boot-time delete that also removed comparisons and their ratings, and
  deleting a workflow is refused while one of its runs is live, paused or
  interrupted with a worktree.
- Full access is now a visible, accessible switch on each agent card, with a
  risk dialog before it is enabled, and the setup wizard has an Access step
  that offers it agent by agent, off by default (KT-975).
- The plugin catalogue offers MySQL / MariaDB (KT-996, #217), through the
  community server `@benborla29/mcp-server-mysql`: host, user and password,
  with an optional port and database, read-only unless a write flag is set on
  the server.
- A skill can declare variables in the Claude Code format: an `arguments`
  list used as `$name` in its body, an optional `argument-hint`, and Kronn's
  label, default and control for each one as JSON under
  `metadata.kronn-variables`. Kronn reads, validates and writes them back
  unchanged, and such a skill shows a "Variabilisé" badge in every skill list,
  its hover naming the placeholders (KT-906).

- The project card says when the branch has lost its audit evidence while
  another commit still carries `docs/.kronn.json`, names that commit and its
  branches, and restores the file on request (KT-993). A Full audit completed
  on this Kronn instance now counts as audited when the branch has no evidence
  file, and the audit timeline lists the audits the file records (another
  instance, an attestation, legacy evidence) with their date and provenance
  instead of staying empty. The timeline loads in one request instead of up to
  thirteen.
- The audit timeline shows what each step cost, as the agent reported it
  (Claude Code's `total_cost_usd`, OpenRouter's `usage.cost`), summed over the
  step's attempts, and the audit's total: exact when every step reported,
  "≥ x $" naming the unknown steps otherwise. A step whose agent reported no
  cost shows Kronn's estimate where one can be made (see the token figure
  below) and "cost ?" otherwise, never 0; an HTTP step where any response came
  without a cost is never shown as a partial sum. Each run records the model its agent served,
  or the configured one labelled as such, and `step_done` carries the step's
  cost (KT-997).

- Saved workflows whose Exec step puts a value into inline code (`bash -c`,
  `python3 -c`, `node -e`…) are now flagged with an "unsafe interpolation"
  badge, and such a step no longer runs: the run stops at that step, after
  the earlier steps, with an error naming the step, the value and the fix,
  whatever started the run (KT-1017). In the workflow, "Suggest a fix" shows
  the rewrite as a diff, the value moved into a separate argument (`$1`,
  `sys.argv[1]`, or `process.argv[1]` after a `--` that ends Node's options),
  and applies it only when you click
  Apply. When no rewrite is provably equivalent (heredoc, single quotes,
  part of a longer string), it says a manual fix is required and gives the
  recipe. Editing something else in such a workflow keeps working. The
  inline Quick Exec sources of a CollectApiData step are checked, counted and
  fixable the same way.

- Workflows name other resources by slug: `{{ref:<kind>:<slug>}}` in any
  templated step field resolves at run time in the run's project, then the
  global scope, for workflows, Quick Execs, Quick APIs, prompts, skills (the
  repository's included), plugins and pages, and an unknown reference fails
  its step before launch. Sub-workflow, trigger, Quick Prompt and Quick API
  ids are written to `kronn/` as `ref:<kind>:<slug>` and stored as local ids
  at save and import, so a workflow published from one machine and imported
  on another runs its targets without editing.
  `GET /api/resources/resolve?ref=&project=` resolves one for a script
  (KT-917).
- One workflow can serve several projects: `project_scope` lists them (or
  all), the project is resolved when the run is triggered (the request's
  `project_id`, the launching discussion's project, one run per project for
  a cron, the project linked to the tracked repository for a tracker), the
  run executes in that project's worktree, and concurrency limits count each
  project's runs apart. The workflow editor chooses this project only, all
  projects or chosen projects (KT-851).
- Each workflow run has its own artifacts directory, `{{run.artifacts_dir}}`:
  Exec steps write screenshots, logs or reports there, and a Claude Code or
  Codex Agent step whose prompt names it reads it without being able to
  write or delete anything in it (the `read_only_repos` policy). It is
  removed with the run (KT-910).
- A workflow Agent step can declare its tools in `agent_settings.tools`:
  Claude Code built-in tools and Kronn tools by name, or none. A declared
  step starts with exactly those, without the other MCP servers, the skill
  listing or the Kronn catalogue and its instructions; an undeclared step
  launches exactly as before. The step editor and `workflow_step_schema`
  expose the setting, validated at save (KT-908).

- Each agent launch gets its own bridge token instead of Kronn's admin token:
  a random value held only in memory, bound to the launch's discussion, task
  execution or workflow run and their project, revoked when the launch ends
  and forgotten on restart. It opens only the routes the `kronn-internal`
  bridge calls, only for resources in its scope (ids in the path, the query
  and the body are checked), never a route that returns or moves a secret,
  and effects are logged with its id. A workflow it triggers without naming a
  project runs for the room's project when the workflow serves it (KT-851),
  and is refused otherwise. Ids are resolved like the handler resolves them
  (`KT-12` included) and an unknown id is refused; lists, searches and lookups
  only show the token's project; an API call runs for the token's project with
  a config that project can see; deleting the room kills its token; the
  WebSocket bus refuses it. The bridge reads `KRONN_BRIDGE_TOKEN`
  first. Loopback requests without a token keep today's trust until the
  per-action human proof ships in 0.15 (KT-1006).
- An Exec step that runs a repository script can declare the script and the
  modules it loads, as paths relative to the repository of the workflow's
  home project (KT-918). Saving refuses a path outside the repository, a
  missing file or a symlink that leads out of it, and records each file's
  content hash, which enters the workflow's approval fingerprint; a save made
  by an agent never records a hash, so only a human approves. Before the
  command runs, Kronn checks every file against its approved hash and copies
  it into the run's artifacts directory; the command runs from that copy,
  with the run's checkout in `KRONN_WORKTREE`. A file changed since approval,
  even by one line, stops the step before anything runs and the error names
  it; the step editor shows each file as approved or changed and re-approves
  the current content on save. A workflow published to `kronn/` names the
  files and their hashes, never their content. Absolute imports and packages
  found outside the copy are not covered.

- Launching a workflow, you can see and change the agent, model and effort of
  each Agent step, for that run only (KT-1025). The launch card, in a
  discussion or on a Live Page, has a folded "Details" block that lists the
  Agent steps with what is planned and the agent and model selectors; without a
  change the launch request is the same as before. The trigger route, the
  discussion and Live Page action launches and the MCP `workflow_trigger` tool
  accept `step_agents`. An unknown or non-Agent step, an agent that is not
  installed or enabled, or tools or a token budget the chosen agent cannot use
  is refused before the run starts, with a message that says why. The choice
  is stored on the run, so it still applies after a Gate approval or a resume,
  and the step shows the agent it really ran on; the workflow is never
  modified.
- API plugins can declare headers sent on every call, whatever their
  authentication: Notion's mandatory `Notion-Version`, Anthropic's
  `anthropic-version`, the `Accept` Heroku requires. Without one, such an API
  refused every request (Notion: `400 missing_version`) and each workflow
  step or agent had to repeat it. Custom API plugins edit them in the new
  **Headers sent on every call** section, as a literal or `${ENV.KEY}` from
  a field; a header given on a call overrides the default. The 🪄 helper and
  the Workflow architect now look for such a header in the docs and propose
  it. Validation refuses credentials, `Authorization`, `Host`, duplicates and
  undeclared fields, and now also runs on bundle imports, which skipped the
  check refusing CLI-token authentication.
- Agents no longer see an API's authentication keys, or keys already sent as
  headers, listed as "pass as query params" in their API context.
- A Custom API plugin's **Test** button now works: it calls the endpoint
  marked as test endpoint (a round button on each endpoint row), which must be
  a `GET` without path parameters. Kronn selects the most likely one by
  default (`/users/me`, `/me`, `/whoami`…, else the first such `GET`), so
  existing plugins pass the test without being edited. It used to answer "No
  side-effect-free authentication probe is declared" for every custom plugin,
  even a working one.

### Fixed

- A native ACP agent (OpenCode, Vibe, Copilot, Gemini CLI, Kiro) that stops
  answering no longer leaves a discussion silent. Opening a session now waits
  90 s, enough for OpenCode's 30 s per MCP server; the 30 s limit lost that
  race to one slow project server, and the turn was deferred and retried
  without a word. A startup phase that times out or returns an error stops the
  agent, settles the turn as failed and posts a translated message naming the
  phase, the error or the project servers it was starting; a stop during
  startup is honoured at once. A native prompt now stops at the configured
  inactivity delay, not 15 minutes, measured on every update the runtime sends
  (tool calls, thoughts, Claude thinking and Codex reasoning, keepalives), not
  on reply text: the discussion and
  workflow timers leave an ACP run's inactivity to it, parallel tool calls
  each keep their wider bound, and the delay applies as soon as the last one
  ends. A project server named by a bare command found nowhere on PATH is no
  longer declared to the session. A stop by Kronn's inactivity watchdog is no
  longer headed by a wrong "Network error" hint.
- The launch card's step details now show the model and efforts of the
  step's tier (economy or reasoning), not always those of the default tier
  (KT-1095).
- An audit step retried after an attempt that did not report a token counter
  no longer shows a cost estimate: the counter stays unknown in the step's
  total instead of reading as complete (KT-1094).
- The taller model menu no longer runs past the bottom of the window: it
  opens on the side where it fits, or the roomier one, and is never taller than
  that side's room (KT-1032).
- Audit quick wins:
  - Translations with a repeated placeholder (for example "2 dossiers
    sélectionnés") no longer show a literal `{1}` (KT-1053).
  - Two quick clicks on Confirm in the automation import dialog import once,
    not twice (KT-1052).
  - In Docker, the gateway now accepts config and discussion imports up to
    512 MiB, like the backend, instead of answering 413 above 64 MiB; CI
    checks that every backend body limit fits under the gateway's (KT-1064).
  - CI runs `git diff --check` on each pull request, and `make check-diff`
    runs it locally (KT-1063).
  - `make test-backend` runs the whole suite CI runs, integration tests
    included; `make test-backend-lib` keeps the quick library loop (KT-1054).
  - A sub-workflow child respects its own workflow's concurrency limit: a
    refused single child fails the step with the reason; a foreach item waits
    for a free slot and is skipped only shortly before the run's timeout (as
    set when each run started), with a SKIPPED signal that the run keeps. A
    resumed child keeps its concurrency key, and no child is started or
    resumed once its parent's cancellation has settled or its run's timeout
    has passed; every guard stop, and every cancellation, settles the run's
    whole subtree of children (KT-1045).
  - Run retention and a bridge token's workflow list read indexes instead of
    whole tables (migrations 221 and 222, KT-1048, KT-1050).
  - A SubWorkflow foreach file must be a relative path inside the worktree:
    absolute paths, `..` and symlinks leading out are refused on save (editor,
    bundle, restore) and at run time. Foreach reads and writes in the worktree
    never follow a symlink or a hard link (best effort on Windows); a write
    replaces the file through a synced temporary file, keeps its permission
    bits (a new file follows the umask) and refuses sockets, FIFOs and
    devices. Known residual, tracked in
    KT-1055 for 0.15: a read can still be fooled by a process that mutates
    the worktree while it runs (KT-1038).
  - The pre-migration backup now includes the writes still in the
    write-ahead log, checked on the WAL file SQLite itself uses (symlinked
    databases included); when another connection blocks that, the upgrade
    stops and says why instead of saving a stale backup (KT-1049).
  - The desktop app refuses to serve an unauthenticated API on `::`, as it
    already did on `0.0.0.0` (KT-1065).
  - The docs-migration success toast shows again (KT-1076).
  - The CORS allowlist has router-level tests, with and without a domain
    (KT-1058).
  - `docs/stack.md`, `docs/repo-map.md` and `SECURITY.md` match the code
    again, and the tech-debt registry lists open items only, each with its
    detail file, checked in CI (KT-1060, KT-1061).

- Security: a web page the operator visits can no longer open
  `ws://127.0.0.1:3140/api/ws` to read the event bus or inject chat messages
  (KT-1033). The upgrade checks `Origin` against exact origins: the frontend
  on the listening port (the desktop's runtime port included), the gateway
  ports only under Docker, the configured domain, the Tauri webview, the dev
  UI under `kronn start-dev` (which now follows `VITE_DEV_PORT` and keeps an
  operator-set `KRONN_DEV_UI_URL`; Vite no longer falls back to another
  port), and a new list of extra origins in
  Settings > Identity for LAN or Tailscale names, checked with a full URL
  parser. A page on another port gets 403, and a frontend still needs the
  token when auth is on, unless strict localhost is off (unchanged rule).
  The frontend now sends the token as a WebSocket subprotocol instead of
  `?token=` in the URL, so no proxy log can hold it (`?token=` is still read
  from older clients, and dropped from the backend span and the gateway
  access log). Heartbeats are answered on their own socket only.
- Docker detection reads `KRONN_IN_DOCKER` only when it is `1` or `true`.
- Security: P2P federation is now a setting, "Accept P2P connections", off by
  default and never turned on by the upgrade (KT-1033). A peer is identified
  only by its invite code, so anyone who knows an accepted contact's code and
  can reach the port could impersonate it until pairing secrets arrive in
  0.15. While it is off, `/api/ws` refuses every non-frontend client, the
  peer routes (`claim-by-token`, `fetch-file`) answer like an unknown code,
  no contact is dialled, pinged or asked to resolve a join code (the join
  says P2P is off), and an attachment transfer in flight is cancelled. The contacts panel says why contacts are offline
  and enables P2P in one click. When it is on, only an accepted contact is
  admitted, checked again at every frame in both directions and every few
  seconds: deleting or refusing a contact cuts its connection. A shared
  discussion travels only between its members (the contacts it was shared
  with, or on a mirror its host): another accepted contact never receives
  it and cannot write into it or ask to sync it, even knowing its id. A
  peer's attachment must name a message of that same discussion; it is
  downloaded to a staging file in the discussion's own folder, under its
  full file id, and published only if the sender is still authorized at
  that moment. A file served to a peer is checked again right before its
  bytes leave, and the transfer stops if the contact is revoked, removed
  from the discussion or P2P is turned off. Turning P2P off also stops
  connections still being set up and join-code claims in flight. A
  mirror joined before this release has no recorded host: join it again
  with its code to resume syncing. An unknown
  code becomes a contact request (shown as "request", capped and expiring,
  never dialled or pinged until you add its code) and counts toward the ban,
  unadmitted sockets are capped per address, and contact addresses must be
  a bare host and port, requested without following redirects.
- An API call's credentials are scrubbed by value from everything it hands on:
  the step output (successful JSON included), URL summaries, errors and the
  call log, through one set built from every value the call takes from the env
  or the credential store, wherever it lands (base URL included: no plugin
  metadata marks a value public, so a summary may show a masked base URL) plus
  the resolved auth and default headers. Each value is masked raw,
  URL-encoded, hex, base64 and JSON-escaped, case-insensitively from 8
  characters, before any truncation; shorter ones only as whole tokens. Notify
  scrubs its whole response before cutting the excerpt, a token-exchange error
  loses every `${ENV.*}` its body used (the same parser as substitution, so
  `${env.password}` counts), and a successful token response is never quoted.
  Stored text (call log, run detail in the UI and through MCP
  `workflow_run_get`, MCP run status) gets URL query/userinfo stripping and
  the secret heuristics, the most that can be done for output written before.
  Known limits: an endpoint that transforms what it echoes defeats any finite
  list of forms, and a short secret glued inside another token stays visible
  (KT-1035).
- Turning a workflow on is a human decision. A workflow imported from a
  `.kronn-workflow.json` file, an accepted agent bundle (parent and children)
  and an accepted agent proposal land disabled, as ADR-005 and the Artifact
  import already required, so a shared Cron or Tracker trigger no longer fires
  on the next tick. An agent's token can no longer enable a workflow through
  create, `workflow_update` or `workflow_set_enabled` (`force` included): the
  backend refuses and tells it to ask the user, and an agent's write can never
  turn a stored `false` back to `true`, even from a read made before a
  concurrent disable. An agent's change to what an enabled workflow executes
  (steps, rollback, trigger, Exec allowlist, actions, workspace, guards,
  variables, concurrency limit and key, project) disables it until the user
  enables it again, and so does its change to what a Quick API sends or what a
  Quick Prompt runs (prompt, variables, agent, connection, tier, skills,
  profiles, directives, agent settings), or its name's slug or its project,
  and so does an agent's (or an import's, Artifact imports included) creation
  of a Quick API or Prompt that shadows a shared `ref:` such a workflow uses,
  for every enabled workflow, in any project, that names it by id or holds a
  `ref:qa:`/`ref:prompt:` (any field, fan-out or rollback position) with its
  old or new slug or any slug it was published or imported under: no reference
  is resolved, so the rule is a conservative superset that ambiguity can never
  block, and it runs in the same transaction as the write. Quick Exec lines
  keep their own KT-1017 rule: an agent's line waits for a human's approval. A
  disabled definition never runs: a parent refuses a disabled sub-workflow,
  manual and TriggerWorkflow launches, the scheduler and trackers skip it and
  only a human can resume an interrupted run of a disabled workflow. Known
  residual: a run already in flight, a human-resumed one included, can still
  load a Quick API or Prompt an agent changed during that run (rollback and
  fan-out included); revision pinning (the approved workflow and its
  dependencies frozen at approval) is a 0.15 priority. With localhost trust on
  (auth off, or `auth_strict_localhost = false`) a local process without a
  bridge token still counts as the user, the trust model already documented
  for the HTTP API; strict localhost is the mitigation until positive human
  authorization (0.15, KT-1034). The import preview shows the trigger, the
  Exec steps and the Exec allowlist, and the agent banners say the workflow is
  created disabled (KT-1037).
- Kronn now says why it turned a workflow off on its own. Each automatic
  disable (an agent's edit or create, an agent's change to a Quick API or
  Prompt it uses, an agent bundle or proposal, an import, a config restore
  that fails validation) is recorded on the workflow with its reason, who and
  when, and a short summary ("steps changed by Codex", "Quick API « fetch »
  edited by Claude"); migration 223. The Automations page shows a banner when
  any is waiting ("N workflow(s) were changed by an agent or imported and are
  disabled by default"), a details panel with each one's reason, summary,
  author, date and link, a Re-enable button per row and a Re-enable all button
  (a Cron or Tracker workflow is always confirmed first), and a reason badge
  in the workflow list. A human enable clears the record. `GET
  /api/workflows/auto-disabled` and `POST /api/workflows/reenable` (one id or
  many) refuse a bridge token (KT-1037).
- Outbound requests to a URL a user, a plugin, a provider or an agent supplies
  go through one guarded transport (`core::safe_http`): ApiCall (every page,
  retry and redirect), OAuth and token exchange, Notify, gate webhooks, media
  asset downloads, remote MCP probes, page `web_fetch` and the GitHub tracker.
  Its resolver checks every DNS answer and connects only to the checked
  addresses, so a rebinding answer cannot reach the socket; IPv4-mapped IPv6
  forms such as `[::ffff:127.0.0.1]` are refused like their IPv4 host. Each
  redirect hop is re-checked; an https→http downgrade and a cross-origin
  307/308 are refused, as is any cross-origin hop that would resend a body (a
  301/302 on PUT included); a cross-origin hop drops every header and query
  key the plugin, the step or the webhook supplied. IPv6 addresses are
  classified against the IANA special-purpose registry (the local-use NAT64
  `64:ff9b:1::/48`, `2001:2::/48`, `3fff::/20`, `5f00::/16` and everything
  outside `2000::/3` are refused). Peer attachment fetches and remote joins
  use the guarded client and never follow a redirect; repository discovery
  uses it with same-origin redirects, and an imported GitLab host never
  receives the process `GITLAB_TOKEN`: a token only goes to the host it was
  configured with (KT-1039).
- A project whose validation discussion has finished no longer stays stuck at
  Audited: that discussion archives itself on its last word, which hid the
  "Mark audit valid" banner, while the audit timeline's "Validate the audit"
  only opened a form to start a whole new validation. The timeline now reads
  the latest run's linked validation (same terminal-signal check as the
  validate gate), validates the audit directly with an error toast on refusal,
  and opens the finished discussion even when archived. A new validation can
  only be started while no finished one is linked.
- A Kronn-launched agent's bridge token is checked against what each request
  really does: ids nested anywhere in a body or in an imported workflow, task
  references, offers, invites and sessions are resolved before the check;
  shared resources (project-less or serving several projects) and the agent
  library are read but never written; created resources land in the agent's
  project and no update can move them out; effects on shared resources run for
  the agent's project only; General discussions stay private to their own
  launches; every response, whatever the verb, is scoped at any depth with its
  counts; the token's project is frozen at first use; every launch gets a token,
  dies when cancelled or after 12 hours; the operator token is compared in
  constant time (KT-1006). An id field the gate cannot resolve is refused, in
  the body and the query; an import hides only the resources it bundles;
  every route that reaches a room through a session (workspace, history lease,
  link, unlink, transfer, accept-offer, session lookups) checks that room
  first, and a token cannot force a session reassignment; a saved Quick Exec
  of another project is refused on import and at run time; a workflow export
  that bundles another project's resource is refused; refusals no longer name
  another project's ids; the bearer scheme is read case-insensitively and a
  token never reaches the peer-only routes; a token's media discussion and
  learning proposal land in its project. A page or room a workflow step names
  through a template must, once rendered, belong to the run's project (a
  literal id still works whatever its project), whoever triggered the run;
  the keys of a caller's own maps (variables, an external API's path, query
  and headers) are no longer read as Kronn ids; the plugin overview and a
  page's feeding workflows show a token only its project's; any discussion the
  token owns, deleted or moved, ends it; its planning writes are recorded as
  an agent's; a plan proposal from a room only touches tasks of that room's
  project alone, creates its tasks there and names the project on its card. A
  token's workflow may publish only into its own project's pages and rooms; a
  workflow run without a project is private to its launch, even after its
  workflow moves into a project, and a token's run lists, run filters,
  `last_run` and duration estimates (workflow and Quick Prompt) only consider
  runs and launches it may see; peer-join cannot
  end a session another project's room holds; a config opted into General is
  usable by project-less tokens as the plugin overview shows; a page slug can
  no longer look like a page id, a page resolves by id first, and a token's
  slug conflict names no other project.
- Processes Kronn starts for a caller no longer inherit the backend's
  environment: agent CLIs on all three routes, the project and discussion
  terminal, workflow Exec steps and workspace hooks, Quick Exec (task
  validations included), the API-call credential CLIs and every `git` process
  (whose repository hooks run inside it) get a built environment (a reviewed allow-list, then the launch's own
  values). No child receives `KRONN_AUTH_TOKEN`, `KRONN_ENCRYPTION_KEK`, or
  another agent's provider key, nor a GitHub token from the backend's
  environment (only a connected project's, native ACP agents included), and
  the backend no longer exports its admin token into its own environment
  (KT-1006).
- Native ACP agents (Gemini, Copilot, Kiro, Vibe, OpenCode) now receive their
  provider key configured in Kronn, a temporary directory beside the project,
  and their room and workflow-step contexts, like the other routes (KT-1013).
  They start only with full access (next entry).
- **Known limitation: OpenCode, Vibe, GitHub Copilot, Gemini CLI and Kiro run
  only with full access.** These native ACP runtimes load repository plugins,
  custom tools, hooks and MCP servers before any permission check, so no
  restricted mode can be promised for them. Every launch (discussion, room,
  workflow step, Quick Prompt, summary, audit, resume, model discovery)
  requires the agent's own setting in Config › Agents › <agent> › Full access,
  read from the saved configuration at each launch for the agent actually
  started (a workflow reviewer does not inherit its author's access, and
  turning the setting off stops the next spawn of a running audit or
  workflow); without it Kronn refuses before starting anything, with a
  translated message that names the setting, and never turns it on itself. The agent pickers say
  "requires full access" beforehand. An audit's own full access no longer
  counts for these agents. A per-launch isolated runner is the 0.15 plan.
- Copilot receives Kronn's bridge through `--additional-mcp-config` under a
  per-launch name, with `--disable-mcp-server kronn-internal`, so a workspace
  `.mcp.json` server named `kronn-internal` is never loaded in its place; a
  step's tool list reaches the bridge on every native runtime. A project server
  named `kronn-internal*` is dropped, and only the exact bridge id the runtime
  registered is trusted.
- The ACP broker no longer takes a tool's identity from `rawInput`, which holds
  the model's arguments on every native runtime.
- A bridge-token request with no body labelled `application/json`, as the
  bridge sends every GET, was refused with "invalid JSON body": every reading
  tool (`disc_meta`…) failed under a bridge token.
- `KRONN_HOST_MCP_SYNC=0` keeps a second Kronn instance (a test or an isolated
  check) from rewriting the host CLIs' global MCP configs
  (`~/.copilot/mcp-config.json`, `~/.codex/config.toml`, `~/.claude.json`,
  `~/.gemini/settings.json`) to its own bridge path.
- The other processes Kronn starts get a built environment too (KT-1006):
  dependency checks (`npm`, `cargo`, `go`, `bundle`, `dotnet`, `poetry`,
  `composer`, Renovate through `npx`, and Composer through Docker) keep only
  their registry, cache and toolchain settings; `gh` and `glab` (PR creation,
  PR lookup, `gh auth token`, GitLab discovery) keep their own login and the
  connected project's GitHub token, and the git they start inherits nothing
  more; `docker compose` keeps only `DOCKER_*`/`COMPOSE_*` settings, so a
  compose file can no longer interpolate a backend secret; the `cargo
  metadata` run before a validation gets the Quick Exec environment; and the
  agent installers, RTK, ccusage, WSL, Tailscale and system probes get the base
  allow-list. `glab` no longer reads a `GITLAB_TOKEN` exported to Kronn and a
  package manager no longer reads a registry token from Kronn's environment:
  both use their own login or config file. Every process now names its
  environment when it is built: a spawn without one does not compile, clippy
  refuses any other way to start a process (the system opener included), and
  the exceptions declared in the design note start in the temporary
  directory, never in a repository. The desktop app follows the same rule:
  its sleep inhibitor and its login-shell PATH probe no longer pass the
  desktop's whole environment on, and its restart relaunches it without the
  admin token or the raw key in its environment. Model and version
  discovery and the document sidecar now get a built environment too (their
  CLI's own login and settings, no credential under any name), and testing
  an MCP server, which may come from a repository's
  `.mcp.json`, starts it with only its own configured values. An
  operator-set `KRONN_ENCRYPTION_KEK` leaves the process environment at
  start, like the admin token. On the desktop, only an allow-list (display,
  locale, paths, proxies and toolkit settings) stays in the process
  environment, so the system webview's helper processes (Linux, Windows)
  never inherit a credential, whatever its name; Kronn and its agents still
  get every variable, and `KRONN_USE_KEYCHAIN=0` keeps working. Kronn
  warns at start when the admin token or the key came from the environment:
  the process's original environment block keeps them readable by
  same-user processes, so the keychain, the key file or `config.toml` are
  safer. npm registry credentials
  (`npm_config__auth` and its registry-scoped forms) are dropped like any
  other secret.
- `git push` and `gh` PR creation use the project's own GitHub connection,
  never the first GitHub MCP token of any project. The token no longer
  appears in the push URL or in a variable a repository hook can read: it is
  sent as a header scoped to github.com, with hooks and credential helpers
  off for that push (KT-1006).
- The project and discussion terminal no longer runs `env` and no longer
  reads outside the project: `cat`, `head`, `tail`, `find`, `stat`, `grep`,
  `rg`, `wc`, `du`, `file`, `tree` and `ls` refuse a path that resolves
  outside it, symlinks followed, and `find -exec`/`-delete` and
  `git diff --no-index` are refused (KT-1006).
- The encryption key can no longer be lost to an unreadable vault or to
  encrypted data the boot did not look at (KT-1007). A denied or locked
  keychain, or an unreadable `encryption_key` file, now stops Kronn at startup
  with a message saying what to do, instead of being read as empty and
  overwritten; a key is generated only when no encrypted column holds data
  (MCP settings, execution-variable snapshots and stored credentials, all
  listed in one registry checked against the database schema). When no key
  decrypts existing data, Kronn keeps running for a restore but holds no key,
  so nothing new is encrypted under a key that no vault keeps. Two keys that
  each decrypt part of the data stop the startup with nothing written, and a
  vault holding another key is never overwritten; starting once with
  `KRONN_REENCRYPT_FROM=<fingerprint>` moves one key's data under the other
  without deleting either. A stored API token that can no longer be decrypted
  locks the API (only health and the recovery routes answer, the interface
  shows a "Kronn is locked" restore screen, and an exposed install still
  starts) instead of leaving it open; a credential start-up that fails locks
  it too. Invalid key values are never adopted, and a missing `config.toml`
  no longer offers a random key that could be kept for good.
- Replacing the recovery passphrase now asks for the current one, and
  `recovery.key` records which key it wraps, so a blob for another key never
  counts as a recovery copy (KT-1007).
- Restoring a recovery passphrase on a running instance no longer swaps its
  key (which split secrets between two keys): an import keeps the source
  machine's recovery data beside this machine's, and Plugins → "Re-encrypt
  imported secrets" re-encrypts the imported secrets under this machine's key
  (KT-1007).
- Reset keeps a copy of the key that `config.toml` still has to hold, keeps
  the API token so a network-exposed instance stays authenticated, clears
  execution-variable snapshots, provider keys and GitHub tokens, and reports
  a failure instead of answering success (KT-1007).
- Re-importing this machine's own backup no longer deletes the stored GitHub
  tokens of its projects, and an import runs in one transaction: a failing
  project or MCP insert leaves the previous data in place (KT-1007).
- While the encryption key is locked, no provider key, connection key or API
  token is ever written to `config.toml` in clear: such changes are refused,
  key auto-discovery waits, and an operator's `KRONN_AUTH_TOKEN` serves the
  session only (KT-1007).
- A key kept only in a `config.toml` backup or a set-aside file is now found
  when it still decrypts some rows, instead of leaving them unreadable; a
  recovery passphrase that protects another key can be replaced without it;
  re-encrypted provider keys appear without a restart; an export of secrets
  without a recovery passphrase warns; a `config.toml` set aside at start is
  shown in the setup wizard; every backup copy is scrubbed even when one
  fails; and key files are synced to disk with their directory (KT-1007).
- Secrets under an older key kept only in a `config.toml` backup, a set-aside
  value or a corrupt `config.toml` are re-encrypted under the key in use at
  start (the file is kept) instead of stopping the start; a set-aside
  `config.toml` and a credential load failure are shown app-wide; a reset
  while the key is locked sets up a new key at once; a provider key whose
  save fails no longer appears saved; and a recovery passphrase from before
  0.14.3, or a damaged one, can be replaced from Settings (KT-1007).
- A lost key with no recovery passphrase no longer holds the rest hostage:
  "set the locked secrets aside and start a new key" keeps them in a file and
  keeps discussions, projects and workflows. A failed reset while locked keeps
  the API authenticated; moves from kept key files wait for a durable copy and
  never stop the start; connection keys, LiteLLM and discovered keys appear
  only once saved; credentials of an unreadable `config.toml` are recovered;
  a forgotten passphrase can be replaced; and the desktop restart keeps the
  operator's API token (KT-1007).
- Secrets set aside when a key was given up come back through "Re-encrypt"
  once the old passphrase or code is offered; starting a new key or a locked
  reset never turns API auth off (or on), keeps a readable token, stops on an
  unreadable key store before writing anything, and removes only rows that are
  still the ones copied (KT-1007).
- A credential store that cannot be written after starting a new key or a
  locked reset no longer leaves the API open; secrets put back from a
  locked-secrets file survive a failed reload; the restore reports what it put
  back and what still waits; and an export warns that locked-secrets files stay
  on this machine (KT-1007).
- The same key written in upper and lower case is one key (it used to stop
  the start-up as "several keys"), a damaged key value no longer crashes the
  start-up, and an older `config.toml.backup` is kept under a timestamped
  name instead of being overwritten (KT-1007).
- A first start that cannot keep a durable copy of the new key (disk full,
  read-only data directory) stops with what to do instead of encrypting
  under a key that would be gone at the next start; an invalid or retired
  `encryption_secret` in `config.toml` is set aside in a side file and never
  keeps the key in use from being saved; a reset while the key is locked
  lets the next start begin fresh; `config.toml.backup` copies rotated from
  0.14.2 lose their plaintext credentials; an unreadable `config.toml` is
  kept aside and its key salvaged (KT-1007).
- The locked screen tells a remote visitor to open Kronn on its machine,
  restores a lost key, or sets a new API token when only the stored token is
  unreadable (even with strict localhost); Settings → Recovery restores a
  locked key and warns when `recovery.key` protects another key or a key
  store holds another key, and a banner appears when secrets are locked
  (KT-1007).
- Kronn on Windows starting Claude or Codex installed in WSL now passes them
  what they need across the boundary: every variable it sets for the launch is
  listed in `WSLENV` (appended to the user's own list), and the Windows paths
  it puts in the arguments, including the bridge command inside the inline MCP
  configuration, the sandbox settings and Codex overrides, become WSL paths.
  `KRONN_WSL_BACKEND_URL` gives a WSL agent a backend URL it can reach in
  WSL2's NAT mode, and a warning says so when networking is not mirrored. A
  Gemini, Copilot, Kiro, OpenCode or Vibe found only inside WSL is refused
  with a clear message instead of failing to start (KT-981).

- An audit step that only calls tools, without a word of text, now shows the
  category of its last action, its call count and its tokens while it works, for HTTP agents and
  ACP agents alike, through one probe; the counters no longer wait for a text
  line. A silent audit stream sends keep-alive comments every 15 seconds,
  which hold the connection without passing for model activity (KT-950).

- Importing a plugin bundle now shows, per plugin whose bundled command line
  differs from the catalogue's, the usual and the proposed command (secret-looking
  arguments masked) with an unchecked "I accept these arguments" box. Consent is
  per plugin, and re-importing an already imported bundle can add it (KT-1010).
- An old single-plugin JSON from the removed per-plugin export dropped into the
  bundle import is recognised and imported as a one-plugin bundle, with the same
  review; a malformed one gets a clear error (KT-833).

- Large databases stay responsive (KT-1019). The token statistics and the
  workflow list no longer read every run's step results to reach a few
  columns: a covering index answers them, on the read connection. Scheduled
  and manual backups copy a read snapshot with `VACUUM INTO` instead of
  holding the write connection, so runs and discussions keep writing during
  the copy, and they are refused with a clear log when the disk lacks the
  space. The backup taken before a migration is written to a temporary file
  and renamed, so a full disk no longer replaces the previous good backup,
  and a failed copy stops the upgrade instead of migrating without a backup
  (`KRONN_MIGRATION_BACKUP=0` upgrades without one, on purpose).
- The desktop release pipeline now fails before building when the pushed tag
  differs from `VERSION` or a version marker is stale, builds the release job
  from the requested tag on a manual run, and expects exactly the installers
  Tauri is configured to produce (`.exe`, `.dmg`, `.deb`) instead of accepting
  an `.msi` or `.AppImage` in their place; the release notes give the real
  `.deb` file name. The release commit is resolved once and the checkouts,
  the dependency review and the full CI gate (label-gated jobs included) run
  on it, and the release waits for that gate (KT-1023).

- Opening a project shows the last known Git and Dependencies results at once,
  with their date, and refreshes them behind a discreet indicator. A failed
  refresh keeps the previous result instead of blanking the block, and the
  Git block renders local status before the pull-request lookup completes
  (KT-989).
- Plugins page: the toolbar button that rescans the projects' `.mcp.json` files
  is labelled as such instead of "Sync", the preview names how many projects
  are affected, and the report after applying lists created, merged, rewritten
  and removed counts (KT-833).
- Turning on full access for GitHub Copilot CLI failed with "Agent does not
  support access flags". The backend now accepts every CLI agent the interface
  offers, and each one describes what it really widens (KT-975).
- A guarded action (save, create, toggle) now always runs with the current
  props and translations instead of the ones from the first render, and a
  browser with disabled or full storage no longer crashes the Discussions,
  Dashboard, Workflows or Settings pages when they remember a preference
  (KT-1022).

- Listing projects no longer writes `docs/.kronn.json` into a checkout that
  only carries legacy audit markers (KT-993): the status is computed in memory
  and the file is written by audits and validations only, atomically.

- A validation no longer asks again about a TD already decided (KT-938): the
  decision a card writes on the TD sheet (confirmed, rejected, accepted
  decision) is read back, so the full validation after a resume asks only
  about new or undecided TDs and names the partial validation it follows. A
  deferred TD is asked again, since its decision was postponed. Re-audits stop
  carrying rejected TDs and accepted trade-offs in the index, and no longer
  report them as missed. A TD whose status sits only in its YAML front matter
  is read and updated too.

- An audit of an already documented repository no longer fails on dead links
  in documents Kronn does not own (KT-1020): moved `docs/legacy/` documents and
  root instruction files with human content outside Kronn's block are
  reported, not blocking, and the repair step never rewrites them.

- Audits no longer receive the `kronn-internal` and Memory MCP servers
  (KT-935): the `.mcp.json` filter and the ACP broker apply the same exclusion,
  so an OpenCode, Claude or Codex audit gets neither Kronn's write tools nor a
  knowledge graph shared across projects, and the refusal is logged instead of
  appearing as a registry error. Discussions on the audited project keep
  their tools.

- The documentation template and audit are more consistent (KT-934): a
  validation is refused while a document Kronn owns still carries a
  placeholder or a `TODO:` marker, the final review skips the `TEMPLATE.md`
  gabarits like the document gate does, and no root redirector is written
  toward a missing `docs/AGENTS.md`.
  The 800-word budget of the documentation skeleton now counts raw words, HTML
  comments included, because that is what the agent reads; the skeleton went
  from 892 raw words to under 800 by dropping comments that repeated visible
  guidance (KT-934).

- A resumed audit keeps the tokens, duration and cost of the steps it inherits
  and names the run that spent them, refuses to start when it cannot record
  them, and warns when the sources moved since the run it continues (KT-1021):
  each audit run now records its commit, branch and source fingerprint. The
  timeline shows a step whose agent reported nothing as unknown instead of
  hiding it.
- An audit step's token figure is now its fresh traffic, uncached input plus
  output, for the agents whose cache accounting Kronn's pricing knows (Claude,
  Codex); for the others it stays input plus output as reported, and the hover
  says the cache split is unknown. Hovering lists uncached input, output,
  cache read, cache write and the total with cache. The live counter, the
  run's total and the audit recap use the same figure; rows recorded before
  this keep the figure they were stored with, without a breakdown. A step whose
  agent reported no cost gets an estimate from Kronn's rate table for the model
  its run served (shown "≈"), only where that split is known; otherwise the
  reason is shown in the user's language. One estimated step makes the run's
  total estimated.
- Natively, a Claude discussion kept none of its project's MCP servers once
  one of them carried a credential in its environment (KT-1003): the whole
  `.mcp.json` was refused. Each authorized server now stays on its own. Its
  environment values reach Claude as `${KRONN_MCP_…}` references in the
  command line and as values in the Claude process's own environment, never in
  argv, ACP payloads or events. A credential passed in `args` still leaves its
  server out.
- Security hardening (KT-1009). The WebSocket no longer streams every live
  event to any private-network peer: without the API token only loopback (or,
  under Docker, the gateway's local clients) counts as the local UI, and
  `X-Real-IP` is honoured only from the Docker gateway (`X-Forwarded-For`
  never). The container no longer writes the GitHub token into `.netrc` for
  gitlab.com (a `GITLAB_TOKEN` feeds that entry, and a stale one is removed).
  Saving an OpenAI key rewrites `~/.codex/auth.json` atomically, owner-only,
  and keeps a ChatGPT login's mode. Key and recovery temp files are created
  owner-only and never follow a planted symlink, scheduled backups are created
  0600, masked values no longer panic on accented or emoji edges, the agent
  read guard also refuses Kronn's key, config, database, `.mcp.json` and MCP
  backups, and exports drop the failure webhook URL.
- A plugin bundle can no longer change the command a trusted plugin runs
  (KT-1010): custom arguments in a bundle replace the plugin's whole command
  line, so an import now drops them, and says so, unless the importer gives
  explicit consent (`accept_args_override`). Custom arguments, which may hold
  tokens, now travel only in the encrypted bundle, and a manual import can no
  longer claim a `mcp-` or `api-` id reserved for the built-in catalogue.
- Native skill and agent files are safer to write (KT-1011): nothing is
  written through a symlinked folder such as a `.claude/skills` pointing
  outside the repository, every front-matter value is quoted so a newline in
  a skill description can no longer add a key like `allowed-tools`,
  concurrent runs on one project no longer drop each other's entries from the
  ownership ledger, and "Migrate to .agents/skills" no longer offers the
  catalogue copies Kronn synced itself as repository skills.
- The desktop app keeps its local port from one launch to the next, so the
  interface settings stored by the browser no longer reset at every launch or
  after "Allow connections from other devices" restarts it (KT-972). The port
  is saved in `desktop-port.json` in the data directory and can be set from
  Settings → Identity ("Desktop port", 1024 to 65535, desktop app only, with
  a "Restart now" button) or pinned there by hand; a busy port is replaced for
  that launch only. The port is held from
  the moment it is chosen, and the app only opens a backend that proves it is
  the one it just started, never another Kronn answering on the same port.
  Interface preferences (theme, tour progress, folds and sidebars, favourites,
  default project, dismissed update) are also kept by the backend and
  restored on a new origin; drafts and unsent messages stay on the device.

- A task delegated to Gemini, Copilot, Kiro or OpenCode as a launched worker
  ran with the discussion's full access and without its delivery context, so
  it could act beyond the worker scope and never deliver (KT-1012). These
  native ACP agents, and Vibe, are now refused as launched workers with a
  clear reason,
  at preparation and at launch; one worker policy now decides every route, and
  an exact joined CLI session of the same agent stays eligible.
- Agent streams are sturdier (KT-1014). An accented letter or emoji split
  across two network chunks of an HTTP model's reply (Ollama, LiteLLM,
  OpenRouter, NVIDIA, Custom) is decoded intact instead of becoming two `�`,
  which could also corrupt a file path in a tool call. A non-UTF-8 byte on a
  CLI agent's output costs one replacement character instead of stopping the
  reader and leaving the agent blocked until the watchdog; a native ACP agent
  keeps its session. After a Claude or Codex turn exits normally, the
  processes it left behind (stdio MCP servers, background commands) are
  stopped with it instead of piling up.
- When Ollama refused an unreadable tool call and Kronn replayed the request,
  the text the refused attempt had already streamed (a preamble before the
  call) appeared twice in the reply and in the saved message (KT-944). On an
  Ollama turn that offers tools, the first 512 bytes of an attempt are now held
  until the attempt ends: a replayed attempt's preamble is dropped, a
  successful one is shown, and a longer answer streams live as before.
- An audit step on a hosted large-context model (OpenRouter, LiteLLM, NVIDIA,
  Custom) could explore for hours, resending its whole history each round,
  because context pressure was never measured on that wire and the write
  window only opened at round 242 of 250 (KT-998). Pressure is now measured on
  a bounded step budget (the smaller of the model's window and 128 000
  tokens), for audits and HTTP workers alike, and an audit step writes its
  deliverable once it has spent 1 500 000 input tokens. Both budgets can be
  set with `KRONN_HTTP_STEP_CTX_BUDGET` and
  `KRONN_HTTP_AUDIT_STEP_INPUT_BUDGET`.
- The cost an agent reports itself is no longer lost (KT-997, backend part).
  Claude Code names it `total_cost_usd`, which Kronn did not read, and the
  default Claude route dropped it anyway; OpenRouter's `usage.cost` was never
  read. Both now reach the run's usage as integer micro-USD, summed per
  response, and stay unknown (never zero) when not reported. The audit
  timeline shows it per step.

- A workflow run now keeps the project it was launched in (KT-1015). A global
  workflow launched from a project lost that project after a gate approval or a
  resume and continued with no working directory, and its worktree was never
  cleaned at startup. A sub-workflow pinned to another project now runs in that
  project's repository instead of the parent's worktree; as a foreach child it
  is refused, since it would have to share the parent's worktree.
- One failing workflow no longer cancels the scheduler's tick (KT-1016): a
  missing variable or an unreachable tracker stopped the loop, and every cron
  occurrence of the workflows after it was lost for good. A tracker issue is
  now marked processed only once its run is admitted, so an issue refused by
  the concurrency limit or the preflight is polled again instead of never
  running.
- Workflow run lifecycle holes (KT-1018). Approving a gate whose worktree had
  disappeared continued the run in the main checkout; it now fails with the
  reason instead. A run paused on a gate keeps its concurrency slot, and a gate
  approval or an interrupted resume is refused while the limit is reached. A
  run that stops on an error removes its worktree right away (keeping a branch
  that holds unintegrated commits) instead of waiting for the next start, and
  a failing or hung `before_remove` hook no longer keeps the worktree. Adding a
  Gate to a workflow another one uses as a sub-workflow is refused, and a
  workflow imported from a repository's `kronn/` folder goes through the
  editor's save rules. Isolated workflow worktrees get the project's agent
  configs (`.mcp.json` and the others), so an Agent step keeps the project's
  MCP servers and its strict MCP config instead of the host's.
- Workflow worktrees no longer pile up as `prunable` entries (KT-985). A
  worktree a step created inside its run's worktree (`.kronn/pr-N`) is now
  removed with it, and at startup Kronn drops the stale entries of its own
  worktrees under `.kronn/` in every project, leaving the user's other
  worktrees alone. A run still keeps a branch that holds commits no base has,
  whether it succeeded, failed or was cancelled.
- A tracker issue title or a step output can no longer run as code in an
  Exec step (KT-1017). Saving a workflow now refuses any template value in an
  interpreter's inline code (`bash -c`, `python3 -c`, `node -e`…, attached
  forms like `-cCODE` or `--eval=CODE` included), except `{{run.id}}` and
  `{{time.now…}}`, and says how to write it safely: pass the value as a later
  argument the interpreter never parses
  (`["-c", "echo \"$1\"", "_", "{{issue.title}}"]`). A new `{{value|sh}}`
  filter renders one single-quoted shell word, but it is a quoting helper, not
  a way into inline code. Quick Exec applies the same rule when it is saved
  (an unchanged stored line stays editable, and still does not run), and the
  Quick Exec form and the workflow editor now show such a refusal in full,
  with the suggested arguments, instead of a generic error.
  A value may also no longer sit where an interpreter still reads its
  options (`["{{mode}}", "{{issue.title}}"]` with `{{mode}}` rendering to
  `-c`): it is accepted only after the inline code of a shell or Python,
  after `--`, or after a script file, and the run-time check verifies the
  rendered command with the origin of each argument. The same rule now
  sees through programs that launch others (`env`, `sudo`, `timeout`,
  `nice`, `xargs`, `busybox`…) and covers other evaluators (`awk`, `sed`,
  `osascript`, `lua`, `tclsh`, `Rscript`, `find -exec`, `git -c`, `ssh`,
  `docker run`, `npx -c`), whatever path or version suffix names them
  (`/usr/bin/python3.12`, `node18`, `.exe`); a value can never be the program
  itself; and the workflow bundle, the workflow and Artifact imports, the
  Quick Exec import and the `kronn/` import apply it too. An unsafe line is
  kept on save only when it is exactly the stored one (same step, position,
  name and arguments), and an unquoted shell value no longer gets an
  automatic fix, since `"$1"` would not print the same thing. It also knows
  that a shell never glues its script to `-c` (`bash -cx SCRIPT`), covers
  `make --eval` and `VAR=value`, `npm`/`pnpm`/`yarn exec`/`dlx`/`install`/`run`
  and `bunx`, `gdb -ex`, `tar --to-command`, `rsync -e`, `curl -K`, `vim -c`
  and `+cmd`, `sqlite3`, `mysql -e`, `psql -c`, `crontab` and `at`, refuses
  any value given to an interpreter it does not model, and refuses an
  `exec_stdin` value when the program runs its stdin as code (a shell or an
  interpreter without a script or inline code). A config restore imports a
  workflow that fails these rules disabled and leaves such a Quick Exec out,
  and its report names each one and why. Option clusters of launchers
  (`sudo -Eu`, `env -iS`), `docker compose`, git options and subcommands
  that run commands (`rebase --exec`, `submodule foreach`, `bisect run`,
  `git config` values), more launchers (`watch`, `flock`, `kubectl exec`,
  `chroot`, `strace`, `parallel`…) and package fetchers (`pip install`,
  `pipx run`, `uvx`, `go run`, `cargo install`, `gem install`,
  `deno run`/`eval`) follow the same rule.
- A run value can no longer reach a program Kronn does not model unless a
  human approved it (KT-1017). Every program is now either known to read
  its arguments as plain data (`echo`, `printf`, `date`…), parsed by the
  classifier, or refused: an Exec step or Quick Exec that passes a template
  value to any other program is refused at save and at run time, with the
  program named. The step editor and the Quick Exec form show a checkbox for
  such a line ("terraform receives values from the run; I confirm it treats
  its arguments as plain data"); only a human can tick it, an agent's save
  never sets it, and a bundle, an Artifact import or an import through an
  agent drops it.
- A run value given to a data-only program that still reads options (`date`,
  `seq`, `basename`…) is refused at run time when it renders to an option
  (`date {{x}}` as `-s…`), unless a literal `--` precedes it (KT-1017).
- A template value glued to an option (`-{{x}}`, `--{{x}}`, `-v{{x}}`) is
  refused unless the option before it is one the program reads a value from
  (`mysql -u{{user}}`, `git commit -m{{msg}}`, `--name={{value}}`) (KT-1017).
  `sqlite3 --cmd`/`--init`, the command after BSD `script FILE`, and jq's
  `-f`/`-L` files are code like `-cmd`, `script -c` and an inline program.
- An `exec_stdin` value reaching a program Kronn does not model is refused
  unless a human approved the step, as for its arguments (KT-1017).
- `run.*`, `time.*` and `now*` are reserved: a workflow, launch, trigger or
  CollectApiData variable of that name is refused at save and at trigger, and
  `{{run.id}}` / `{{time.now}}` always resolve to Kronn's own values
  (KT-1017). An approved CollectApiData source is no longer refused at run
  time.
- Kronn now trusts a run value without a human only in five exact shapes,
  `bash|sh -c SCRIPT NAME ARGS…`, `python3 -c CODE ARGS…`,
  `python3 SCRIPT ARGS…`, `node -e CODE -- ARGS…` and `node SCRIPT ARGS…`,
  with no other option and no value in the code or script, and in data-only
  programs (KT-1017). Every other line, `git`, `make`, `docker`, `npx`,
  `bash -ec` and wrappers included, needs a human's approval; code, option
  and program positions stay refused, approved or not. A templated
  `exec_stdin` is trusted only by data-only programs and those shapes; a
  shell reading its program from stdin, `/dev/stdin`-style scripts, `-f -`
  and `python -i` count as code.
  An approved Quick Exec now runs from its page, and "Suggest a fix" moves
  shell flags such as `-e` into a `set` line.
- An Exec line or Quick Exec an agent writes (bridge token or Kronn's agent
  tools) needs a human's approval for any run value, whatever its shape
  (KT-1017): the server records who last wrote each line, and an agent can
  neither approve nor launder a value through a `bash -c` script. Such a line
  is saved and the run refuses it until a human approves it; the save result
  names the waiting steps (`notice`, `kronn_notice` for agents), and the
  workflow page and step editor show "written by an agent: needs your
  approval" with the line and an approve action. Inline CollectApiData
  sources get the approval box in the editor.
- Whatever originates from an agent is agent-written, whatever the transport
  that carries a bridge token or an agent signal (KT-1017; residual for 0.15:
  a local agent that drops its bridge token is still trusted as a human through
  loopback): a `KRONN:WORKFLOW_READY` or `BUNDLE_READY` proposal accepted by a
  click, and a new or changed line of a `kronn/` file, wait for an approval in
  the editor; the creation banner and the approve sheet list the Exec lines.
  The writer is tracked per line (main, setup, stdin, each source) and a line's
  identity includes its step type and pinned scripts; fields a step type never
  reads are dropped on save. An agent never supplies a script hash, and an
  empty hash is pinned only by a human's save of that step. A human can approve
  an agent-written Quick Exec (writer and approval are stored apart). A config
  restore keeps approvals only from a backup this instance sealed. Launchers
  that start a default shell (`su`, `script`, `chroot`, `unshare`, `nsenter`,
  `sudo -s`, `newgrp`, `sg`) and stdin paths climbing to `/dev` count as code
  readers, and a script fed by a templated stdin must be a regular file at run
  time. `date` options are matched in clusters and abbreviations. Option
  values never hide a default shell (`sudo -u u -s`, `script -t 0 FILE`), an
  `awk|sed|make|psql -f` or `lua`/`Rscript` program file fed by a templated
  stdin must be a regular file, script hashes carried by an agent-origin import
  or an unsealed restore are blanked, the stdin line has its own writer, and
  the restore seal is compared in constant time. A script
  shape (`python3 tool.py …`) needs its script pinned in `exec_script_files`,
  checked at run time to be a regular file; a shell script given to `-c`
  never starts with `-` or `+`; shells are not trusted shapes on Windows.
  Only `echo`, `printf` (literal format), `test`, `true`, `false`,
  `basename`, `dirname`, `seq` and `date` stay data-only: programs reading or
  writing paths (`cat`, `grep`, `jq`, `cp`, `tee`, `rm`…) or the environment
  (`printenv`) need the approval. Every import drops an approval except a
  restore of this instance's own backup; the `kronn/` import keeps only one
  this instance gave to the very same line. One approval covers the whole
  step: the editor lists every line it covers and an approval no line needs
  is dropped on save. Agent background jobs run a saved Quick Exec with the
  same checks, and a code reader behind `npx`, `kubectl exec -i`,
  `docker compose run`, `runuser`, `xargs` or `parallel` stays refused when
  approved.

- Security: dependency advisories. Frontend overrides now pin `sharp` 0.35.5
  (GHSA-wq5f-xc86-pv6w, librsvg), `source-map-js` 1.2.2 (GHSA-68fv-2mgg-jv7q,
  denial of service), `dompurify` 3.4.16 (GHSA-p98j-92pf-mc4p,
  GHSA-6688-9rhm-gjv2) and `global-agent` 4.1.3, which drops the `sprintf-js`
  chain behind GHSA-hp3w-g68c-fv3c (no patched `sprintf-js` exists). Only
  `onnxruntime-node`'s install script uses `global-agent`.

### Changed

- Settings: in every agent card (agents, Ollama, external APIs) each tier's model picker now spans the card's full width, one tier per line, and its list opens taller. Model names share long provider prefixes such as "OpenCode Zen/…" and were unreadable in the narrow control.
- Agents no longer receive the machine's GitHub token by default (KT-1006, D2):
  `GH_TOKEN`, `GITHUB_TOKEN` and `COPILOT_GITHUB_TOKEN` reach Claude and Codex
  launches only for a connected project, and are removed from the inherited
  environment otherwise, Docker included. On upgrade, existing projects whose
  remote is on GitHub stay connected through the gh login and show a one-time
  notice with a "Turn off" button; new projects start not connected. Workflow
  Exec steps follow the same project setting, and a failed `gh` step says the
  project is not connected. `gh auth token` now runs asynchronously, with a
  5-second limit and a one-minute cache, instead of blocking every launch.
- A request whose bearer token matches neither the operator token nor a live
  bridge token is refused, even from loopback, where a request without any
  token keeps passing (KT-1006).
- The project and discussion terminal runs its command without a shell:
  quotes still group words, but `$VAR`, `~` and globs are passed literally
  (`find . -name "*.rs"` still works) (KT-1006).
- `config.toml` no longer holds secrets (KT-1007): provider and External API
  connection keys and the API auth token are stored encrypted in the database,
  and the encryption key stays in the OS keychain or the `encryption_key` file,
  kept in `config.toml` until two independent copies exist (two key stores, or
  one plus a recovery passphrase for that key); a sidecar-only install
  without a passphrase keeps it there. The first start moves
  existing values, checks each one decrypts back, keeps the previous file
  encrypted as `config.toml.pre-credential-store.enc`, and can be interrupted
  and rerun without loss. The `config.toml.backup` copy made before database
  migrations is written owner-only and without credentials. Downgrading to
  0.14.2 hides the stored keys until the next upgrade. See
  `docs/operations/key-management.md`.
- Plugins page: one export and one import flow, the plugin bundle, where each
  plugin's scope and CLI exposure are chosen on import. The per-plugin JSON
  export and the paste-a-spec import are gone, and a plugin is deleted from
  its detail sheet only, behind its two-step confirmation (KT-833).
- The release notes are now generated from the installers a release really
  carries (KT-1004): one direct download link per attached file, the version's
  CHANGELOG section above the install table, then the Linux and macOS install
  notes. A missing platform or CHANGELOG section fails the
  release job instead of publishing a draft that names absent files.
- A workflow launch card, in a discussion or on a Live Page, shows only the
  values you can fill as fields. The values Kronn resolves itself go into one
  folded line, "N values resolved at launch", closed by default, that lists
  each one's name, description and origin, without greyed-out fields
  (KT-1024).

## [0.14.2] - 2026-10-03

### Added

- The project card's Audit tab is now a timeline (KT-977): briefing,
  template, the audit's steps grouped as the pipeline runs them (documentation
  core, specialised audits, consolidation), validation, validated. Every step
  shows, before it runs, the file it writes and what it covers (from the new
  read-only `GET /api/audit/steps`, built from the chain the pipeline runs),
  then its status, a readable reason when it failed or was interrupted, its
  duration and when it last ran. A resumed run is shown whole: its steps are
  merged with the run it continued. A failed group resumes from its own button,
  and the timeline says that the consolidation reruns after it. The agent panel
  groups installed CLIs (recommended), HTTP agents and local models, shows the
  model of each level (⚡ Eco, 🎯 Standard, 🧠 Advanced, the recommended one)
  and warns about lower levels, local slowness and data sent to an HTTP
  provider. The briefing is filled in and saved from the timeline without
  opening a discussion, and a note explains what changed in the audit.

- Any HTTP agent can now run an audit, at the user's choice (KT-980): NVIDIA
  and named connections such as OpenRouter join Ollama and LiteLLM. Full and
  partial audit requests take an optional `connection_id`; the audit uses that
  connection's endpoint, key and model for the chosen level, and its
  validation discussion keeps the connection. A `Custom` agent without a
  connection is refused by name. Vibe, which has no file tools, still cannot
  audit.

- The Automation page lists the skills. "Skills" is a fifth type in the
  sidebar Filter, with its count, next to Workflows, Quick APIs, Quick Prompts
  and Quick Execs, and covers the built-in skills as well as the ones you
  wrote. A skill sits under every project that lists it among its default
  skills, and under "No project" when none does; the flat lists (Favorites,
  Recent) show it once. Opening one shows its sheet in the main column: name,
  description, category, the projects that use it, and its `SKILL.md` — rendered
  as Markdown without raw HTML (the same rendering as a repository resource) or
  as Source. Name and description are searchable, like the other types. The
  sheet is for reading: no variable, no launch. A skill is still edited in
  Config › Skills, which the sheet links to; a skill you wrote can be deleted
  from its row or its sheet, a built-in one cannot. A skill has no pin on the
  server, so its favorites are kept in this browser. Merging skills with Quick
  Prompts stays KT-906 (KT-914).

- Skills are real Agent Skills, written where agents look for them. Writing a
  skill into the repository now produces `.agents/skills/<slug>/SKILL.md` in the
  standard format (valid `name` and `description`, Kronn's own fields under
  `metadata`), never `kronn/skills/`, which keeps only what has no native home.
  A repository that already has `