# Changelog

All notable changes to `mgf-test-web` (ProofWarden) are documented here. The format
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### In progress — the rest of the authority web (M5)

- **CI** — `testctl ci start`: the real CI-run store + per-pipeline `…/pipeline/<id>`
  deep-links (the read-mostly console holds no trigger token, per gate G2).
- The production **`mgf-test-supervisor`-as-library** runner adapter (D-MTEST-API; a
  swap-in for the spike's `pytest_runner`).

> Not yet released. See the plan + spec in
> [`mgf-fedres/docs/planning/mgf-test-web/`](https://codeberg.org/magogi-admin/mgf-fedres/src/branch/main/docs/planning/mgf-test-web).

## [0.1.4] — 2026-07-06

### Added

- **Running view — live log streaming (SSE) [spec M3 / console PLAN Phase A].** The
  live console now streams a run's NDJSON in **real time** over Server-Sent Events
  (`GET /p/{slug}/stream/{run_id}?since=<seq>`), tailing the file the warden appends
  to — resume-from-`seq`, so a dropped connection loses nothing (the file *is* the
  record, spec §4.3). A small vanilla-JS module (`live-console.js`) appends lines
  with **auto-scroll + Pause** and a **per-run selector** when multiple independent
  runs stream. **Progressive enhancement:** the 3-second `<meta refresh>` stays as
  the JS-off fallback and the script removes it only when it takes over — JS-off
  behavior is unchanged. Telemetry-only: authority state (the lease cards) stays the
  warden's. Verified live — an event appended *after* the connection opened streamed
  through, and a terminal `test_end` closes the stream.

### Changed

- **Zig catalog enumerates test *tiers*, not just the `test` step.** In addition to
  the conventional `test` step (+ any `test`-prefixed), the zig runner now lists the
  known test tiers a repo defines — `safety`, `smoke`, `conformance`, `fuzz`,
  `stress`, `e2e`, `integration` (`_zig.is_test_step`). Non-test steps
  (`install`/`run`/`docs`/…) stay excluded. Verified on the fleet: soothfast →
  `[safety, test]`, soothwatch → `[conformance, e2e, smoke, test]`, soothtroth →
  `[fuzz, test]`. The name set is a precise default; a repo whose tier is named
  otherwise is a candidate for a future repo-declared override.
- **CI view — real, config-driven Woodpecker links [spec M5 / console PLAN Phase B].**
  The CI view's Watch links were a hardcoded placeholder host
  (`woodpecker.magogi.local`) and a wrong URL pattern (`/repos/mgf/<slug>`). Now the
  host + repo come from an optional `[ci]` section in a project's
  `fed-resources/source/project.toml` (host defaults to the fleet's real Woodpecker,
  `195.15.241.48.sslip.io`); Watch links point at the repo's real CI page
  (`<host>/repos/<repo>`), with **honest-absence** when unconfigured. The Running view
  now flags a CI-origin lease as **"CI · joined lease"** (single-flight holds across
  CI + local). Per-pipeline `…/pipeline/<id>` links + a real CI-run store still need
  `testctl ci start` (a separate testctl/M5 piece — the read-mostly console holds no
  trigger token, per gate G2).
- **History view — filter by test + origin [spec §5.3 / console PLAN Phase C].** The
  History grid filtered by result only; it now also filters by **test** and
  **origin** via a small server-rendered form of selects (no JS). The result pills
  carry the active test/origin so the three filters compose; a `clear` link resets
  them, and an unmatched combination shows honest-absence.
- **Catalog config — Reset-to-defaults + live effective-config preview [console PLAN
  Phase C.2].** The per-test config panel gains a native **Reset to defaults** button
  (a `type="reset"` — works JS-off) and, with JS, a live **effective:** preview of the
  overlay as you edit (`catalog-config.js`). Progressive enhancement: the form + reset
  work identically without JS; the preview is a read-only addition.
- **Stale-catalog banner.** `testctl catalog` now writes a volatile provenance
  sidecar (`generated/test_catalog.provenance.json` = `source_commit` +
  `generated_at`) alongside the deterministic catalog, and the **Catalog** view
  shows an advisory banner when that commit no longer matches the repo's current
  `HEAD` — read passively from `.git` (loose refs + `packed-refs`, no git binary),
  so the console flags a catalog that *may* predate recent test changes and points
  you at `testctl catalog`. The sidecar is gitignored (like `live/`): its timestamp
  would churn the regenerate→byte-identical gate and its commit would false-positive
  the banner the instant a catalog commit advanced `HEAD` — so it stays local and
  the catalog itself remains byte-identical. When the sidecar is absent or a commit
  is unreadable, no verdict is shown (honest absence, never a wrong "fresh").

## [0.1.3] — 2026-07-06

### Added

- **Zig runner — ProofWarden now covers the fleet's Zig repos, not just pytest.**
  `testctl catalog` on a Zig project (`build.zig`) enumerates its test steps via
  `zig build --list-steps` (routed by `detect_project_kind`), emitting a
  `runner: "zig"` catalog; the warden runs a step through a new `zig_runner`
  adapter (`zig build <step>`), single-flight/streaming/audit identical to pytest.
  The Zig toolchain is resolved at call time (`_zig.zig_cmd`: a real `zig` binary,
  else `python -m ziglang`, each probed). Enumeration is **step-level** — Zig 0.16
  has no per-test listing (`zig build test --list` is rejected), so the unit is the
  conventional `test` step (+ any `test`-prefixed); richer tiers
  (`safety`/`conformance`/…) are a future repo-declared convention. A missing
  toolchain or a `build.zig` that can't evaluate (unfetched git deps, a
  `minimum_zig_version` newer than the resolved zig) surfaces an honest error, not
  a hollow catalog. Verified across the fleet: 8/12 Zig repos enumerate cleanly and
  `zig build test` runs to a pass; the rest error honestly on environment/version.

## [0.1.2] — 2026-07-06

### Fixed

- **`testctl catalog` detects the project's test runner instead of forcing pytest.**
  A repo with a pytest marker (`pyproject.toml`/`setup.py`/`setup.cfg`/`pytest.ini`/
  `tox.ini`) is enumerated with pytest as before. A **Zig** repo (`build.zig`) or a
  repo with no recognized test project is now **skipped cleanly** with an honest
  message and a zero exit — instead of running pytest and surfacing a misleading
  "install pytest in .venv" error for a project that never had Python tests. This
  stops the Zig-heavy Sooth family (and the 6 MGF Zig repos) from erroring under
  `testctl catalog`. The spec's zig runner (§4.1, `zig build`) is still
  unimplemented — a scoped follow-up (Zig 0.16 has no per-test enumeration, and the
  warden has no zig execution path yet).

## [0.1.1] — 2026-07-06

### Fixed

- **`testctl catalog` enumerated tests with the wrong interpreter.** Collection ran
  under testctl's *own* interpreter (`sys.executable`) — which, installed as an
  isolated `uv` tool, has no pytest — so every real project enumerated to a silent
  **"0 tests"**: a stale interpreter masquerading as an empty repo. Collection now
  runs under the *project's own* `.venv` via a shared `_pyenv.pytest_cmd` — the same
  interpreter the warden runs with, so the catalog lists exactly what a run will
  execute. And a genuine collection failure (missing pytest, a `minversion`
  mismatch, an import error in a test module) now raises `CollectionError` — the
  command reports *why* and exits non-zero instead of writing a hollow empty
  catalog; pytest's real "no tests collected" (exit 5) stays a legitimate empty
  result.

### Changed

- **Outcome-vocabulary fidelity (M2-P1 §1.6).** The runner no longer flattens the
  supervisor's outcomes: `hung`/`crashed`/`quarantined` stay distinct through the
  warden result, the live NDJSON events, and the console (History/CI chips —
  hung/crashed error-class, quarantined warn-class, brand tokens only). The single
  mapping lives in `warden/outcomes.py`, pinned against the *installed*
  supervisor/mgf-common vocabularies; an unmapped outcome now reports **loudly** as
  `unknown` (an `error` event names it), never silently as `error`. The durable
  run-record still projects onto the fedres 0.1 `test_status_value` enum
  (`hung`/`crashed` → `error`, `quarantined` → `skipped`) but preserves the
  verbatim outcome on its audit trail (`{"event": "outcome", "note": "hung"}`),
  which History renders; widening that enum is the spine's call — a PAPER for
  [mgf-fedres/FEEDBACK.md](https://codeberg.org/magogi-admin/mgf-fedres/src/branch/main/FEEDBACK.md),
  never a local schema edit.
- **Console mark — mgf-brand-web 0.5.** The console shell walked the brand kit to
  0.5, bringing the living/breathing Magogi mark into the header. (#5)

## [0.1.0] — A4, the single-flight warden spike

> Initial pre-alpha cut (Development Status :: 2 — Pre-Alpha); not yet tagged or
> published. The A4 milestone is the core that defines the product: **a given test
> can never run twice at once, regardless of launcher**, enforced *by construction*.

### Added

- The **warden broker** (`Warden`, `AcquireOutcome`) — the per-project resident,
  **sole executor**. Single-flight is enforced *by construction*: the acquire
  decision (`free → grant` / `same test + config → coalesce` / `else → queue`) is an
  in-process critical section, with **no file-lock TOCTOU**. Every outcome
  (acquire / coalesce / queue / release / reclaim) is an audited event.
- The **lease registry** (`LeaseRegistry`, `Lease`, `RunRequest`) — the atomic
  single source of truth for "what's running": running leases keyed by project (or
  by `test_id` for `independent` tests), the FIFO queue, and the rolling audit.
  Heartbeat + TTL + **audited reclaim** for warden-crash recovery (never a silent
  steal). Serializes to the `test_status` wire shape for read-visibility.
- The **cross-process authority** (`WardenDaemon`, `WardenClient`,
  `WardenUnreachable`, `is_live`) — a Unix-domain-socket daemon makes one process
  the executor for **every origin** (CLI / web / agent / CI). Exactly one warden per
  project via an `fcntl` singleton lock + stale-socket reclaim; the client **fails
  closed** (raises `WardenUnreachable`) when the warden is down — never runs a test
  on its own.
- **`testctl`** — the project contract surface (`serve` / `run` / `status`).
  `run` is **fail-closed** (refuses, nonzero, never spawns a test when the warden is
  down); `status` reports an honest `broker:down` rather than an error that looks
  like nothing.
- The **runner seam** (`pytest_runner`) — the spike's real runner: subprocess-runs
  pytest for one node-id (argv list, `shell=False`) and streams its output as
  events. A swap-in for the production `mgf-test-supervisor`-as-library adapter
  (same signature).
- The **public surface** — `mgf.test_web` re-exports the warden API (`Warden`,
  `WardenClient`, `WardenDaemon`, `WardenUnreachable`, `RunRequest`, `Lease`,
  `LeaseRegistry`, `AcquireOutcome`, `is_live`) and ships `py.typed`; importing the
  package requires only the stdlib warden core (no `mgf-fedres`, no FastAPI).
- The A4 acceptance suite — concurrency-marked proofs that two concurrent launches
  yield **one** execution + one `coalesced` record, a different test queues, a dead
  holder is reclaimed (audited), and a down warden **refuses**.

[Unreleased]: https://codeberg.org/magogi-admin/mgf-test-web/src/branch/main
[0.1.0]: https://codeberg.org/magogi-admin/mgf-test-web/src/branch/main/CHANGELOG.md
