Gladex Agent Logs
Agent run logs & app logs · env: prod · LAN-only investor surface
Overview
| Run logs | 525 files, 18 MB |
| Latest run log | run-20260926-043030-123.log |
| Log directory | /data/agent-logs |
| App log directory | /opt/startup/prod/logs |
Run logs (newest first, last 50)
| File | Size | Modified (UTC) |
|---|---|---|
| run-20260926-043030-123.log | 188 KB | 2026-09-26 02:56:56 |
| run-20260926-032802-122.log | 338 KB | 2026-09-26 02:20:30 |
| run-20260926-024118-121.log | 334 KB | 2026-09-26 01:18:02 |
| run-20260926-020038-120.log | 273 KB | 2026-09-26 00:31:18 |
| run-20260926-015037-119.log | 153 B | 2026-09-25 23:50:38 |
| run-20260926-014036-118.log | 153 B | 2026-09-25 23:40:37 |
| run-20260926-013035-117.log | 153 B | 2026-09-25 23:30:36 |
| run-20260926-012035-116.log | 153 B | 2026-09-25 23:20:35 |
| run-20260926-011034-115.log | 153 B | 2026-09-25 23:10:35 |
| run-20260926-010033-114.log | 153 B | 2026-09-25 23:00:34 |
| run-20260926-005033-113.log | 190 B | 2026-09-25 22:50:33 |
| run-20260926-004031-112.log | 153 B | 2026-09-25 22:40:33 |
| run-20260926-003030-111.log | 153 B | 2026-09-25 22:30:31 |
| run-20260926-002029-110.log | 153 B | 2026-09-25 22:20:30 |
| run-20260926-001029-109.log | 153 B | 2026-09-25 22:10:29 |
| run-20260926-000025-108.log | 190 B | 2026-09-25 22:00:29 |
| run-20260925-235025-107.log | 153 B | 2026-09-25 21:50:25 |
| run-20260925-234024-106.log | 153 B | 2026-09-25 21:40:25 |
| run-20260925-233023-105.log | 153 B | 2026-09-25 21:30:24 |
| run-20260925-232022-104.log | 153 B | 2026-09-25 21:20:23 |
| run-20260925-231021-103.log | 153 B | 2026-09-25 21:10:22 |
| run-20260925-230021-102.log | 153 B | 2026-09-25 21:00:21 |
| run-20260925-225020-101.log | 190 B | 2026-09-25 20:50:21 |
| run-20260925-224019-100.log | 153 B | 2026-09-25 20:40:19 |
| run-20260925-223018-99.log | 152 B | 2026-09-25 20:30:19 |
| run-20260925-222017-98.log | 152 B | 2026-09-25 20:20:18 |
| run-20260925-221016-97.log | 152 B | 2026-09-25 20:10:17 |
| run-20260925-220015-96.log | 152 B | 2026-09-25 20:00:16 |
| run-20260925-213653-95.log | 141 KB | 2026-09-25 19:50:15 |
| run-20260925-205157-94.log | 389 KB | 2026-09-25 19:26:53 |
| run-20260925-195858-93.log | 517 KB | 2026-09-25 18:41:57 |
| run-20260925-192850-92.log | 321 KB | 2026-09-25 17:48:58 |
| run-20260925-185030-91.log | 325 KB | 2026-09-25 17:18:50 |
| run-20260925-180536-90.log | 232 KB | 2026-09-25 16:40:30 |
| run-20260925-173957-89.log | 252 KB | 2026-09-25 15:55:36 |
| run-20260925-171044-88.log | 201 KB | 2026-09-25 15:29:57 |
| run-20260925-163300-87.log | 247 KB | 2026-09-25 15:00:44 |
| run-20260925-160013-86.log | 175 KB | 2026-09-25 14:23:00 |
| run-20260925-153430-85.log | 158 KB | 2026-09-25 13:50:13 |
| run-20260925-152430-84.log | 152 B | 2026-09-25 13:24:30 |
| run-20260925-151428-83.log | 189 B | 2026-09-25 13:14:30 |
| run-20260925-150428-82.log | 152 B | 2026-09-25 13:04:28 |
| run-20260925-145427-81.log | 152 B | 2026-09-25 12:54:28 |
| run-20260925-144426-80.log | 152 B | 2026-09-25 12:44:27 |
| run-20260925-143426-79.log | 152 B | 2026-09-25 12:34:26 |
| run-20260925-142425-78.log | 189 B | 2026-09-25 12:24:26 |
| run-20260925-141424-77.log | 152 B | 2026-09-25 12:14:25 |
| run-20260925-140424-76.log | 152 B | 2026-09-25 12:04:24 |
| run-20260925-135423-75.log | 152 B | 2026-09-25 11:54:24 |
| run-20260925-134422-74.log | 152 B | 2026-09-25 11:44:23 |
Tail — run-20260926-043030-123.log (last 200 lines)
**Recursion guard (H)**: three invocation helpers are pinned — `run_sandbox` must inject `--tests-dir "$SB/`, and `run_live` / `run_default` must never name a tests dir; every `run_live` / `run_default` call site must carry `--only` or `--list`. The helpers' bodies are pulled out **by function name** (`helper_line`), because a guard that greps its own source matches its own needle and can only ever fail — which is what the first draft did.
**Dependencies**: bash, python3 (stdlib), git; no network, no sqlite, no systemd.
-**Tests / mutations**: see `## regression-run` — 110 assertions, 6 mutations each confined to its own section.
+**Tests / mutations**: see `## regression-run` — 191 assertions, 12 mutations (6 original + 6 baseline), each confined to its own section.
-**Status**: Green ✅ 2026-09-26 (110/110)
+**Status**: Green ✅ 2026-09-26 (191/191)
> build · mimo-v2.6-flash-free
[0m$ [0mcd /data/repo && grep -n "^\- Live:" tools/REGISTRY.md | head; echo "---"; grep -n "files_total\|entries 70" tools/REGISTRY.md | head
56:- Live: `domain-availability-check gladex.de` → exit 3 (taken); a free `.de` → exit 0; `--format human gladex.de` → `TAKEN` with all three sources listed.
104:- Live: `budget-show` → 0; `--format json` → balanced 5.00 − 1.50 = 3.50; `/api/budget` on :8000/:8001 → same numbers (PHP parses the ledger independently)
164:- Live: `dns-verify gladex.de` → exit 0, `gladex.de BOGUSTYPE` → exit 2 (was 0 + fabricated record), `--server 203.0.113.1 --timeout 1` → exit 4 (was 3), `this-domain-should-be-available-12345.de` → exit 3 (no records), `--format human … NS` → human-readable NS records
664:- Live: `promote-dev-to-prod --dry-run` → exit 5 while `reviewer-to-main/` is empty
752:- Live: `ip-drift-check --format json --domain gladex.de` → exit 0, `drift:false` (tunnel up); `--help` → 0.
1791:- Live: `smtp-relay-probe` → exit 0; `:587` `:465` deny `554 5.7.1`, public path denies `454 4.7.1`, all four endpoints `banner_ok=true`; `--format human` → per-endpoint verdict + rcpt reply
1842:- Live: `source-sync-check` → `exit 0`, 42 files across dev+prod in sync (after the 2026-09-24 drift fix); same via `/data/tools` → `repo=/data/repo`, exit 0 (was exit 3)
1949:- Live: HEAD → exit 0 (176 files — **174 → 176 with `[0.4.65]`**, its own `tools/regression-run` + `tests/test_regression_run.sh` going from untracked to tracked (`linted.bash` 24 → 25, `linted.python` 11 → 12), following **173 → 174 with `[0.4.64]`**; the pre-commit run reads **174** with `bash 24` / `python 11` because those blobs are not there yet — the stale-live-number class, re-read after the commit below — 45 module files compile clean in ~1.6s) with `changelog_version` = `attempted true, entries 70, unique 70, unparseable 0, duplicates [], ok true` (**69 → 70 with `[0.4.65]`**, following **68 → 69 with `[0.4.64]`**: the count is read from the COMMITTED blob, so it is re-read after the append commits rather than carried over — it read 68 while `[0.4.64]` was still uncommitted, which is the whole point of re-reading; the working-tree count was verified the anchored way, `grep -c '^## \['` → 69 with `uniq -d` empty, because the greedy `sed 's/.*\[\([^]]*\)\].*/\1/'` form reports **5 phantom duplicates** on this tree — 0.4.31/0.4.40/0.4.44/0.4.49/0.4.62 — by taking the last bracket in a heading that cites other versions. The **file** count is 173, not the 171 drafted mid-run: `[0.4.63]`'s own two mailbox files were untracked when that number was written and tracked once it committed, so 171 → 173 is this entry's own movement rather than a count carried); `--sha 219fd8f` → exit 1 naming `tests/test_mailbox_a11y.php:208 [php]`; `--sha 395b9b5` → exit 1 naming `.../status_test.go:5 [go-compile]`; unknown rev / non-repo → exit 3; `--sha probe-r9` → exit 3 with `requested_sha: probe-r9` (the R16 parity reference, live); `--format yaml` → exit 2 with **usage on stderr and an empty stdout** (no format could be read, so none is invented — `[0.4.62]`); **`--format json` + a bad argv → exit 2 with a 384-byte 15-key object on stdout and 0 bytes on stderr** (`--timeout abc`, `--nonsense`, `--timeout 0`, empty `--sha`, and the failing token placed *before* `--format`), while the same argv in human mode still gives argparse's `usage:`+`error:` with **stdout 0 bytes**; `--help` (**106 lines**, was 57 before the epilog stopped carrying its own prose, 102 before the exit-3 clause widened, 103 through `[0.4.61]`, **+3 for `[0.4.62]`'s widened exit-2 clause**) renders all seven docstring blocks verbatim under their labels; a copy with a heading renamed → `repo-lint: ERROR refusing to run - docstring section missing or empty …` + ` 'env:' -> 'Environment:'` → **exit 3**, stderr only, and under `--format json` the **same refusal as an object** — `ok false`, `exit_code 3`, `error` naming the pair, **stderr 0 bytes** — which is the `| jq` consumer `[0.4.60]`'s queue described, now parsing (`--format=json` → 3; `--form json` → 3; `-- --format json` → no JSON on stdout; `--format yaml` on the broken copy → refusal only, no `usage:` leak) — **and on that same broken copy `--format json --timeout abc` now yields the 458-byte refusal object with `exit_code 3` instead of falling back to `human`**, which is `[0.4.61]`'s own queue item struck as actioned.
2008:- Live: `./tools/regression-run` → **40 suites, 3394 passed, 0 failed, 0 skipped**, exit 0, 4m31s, shape histogram `bare=2, fence=30, results=2, suite=6`. The movement over `[0.4.65]`'s **3313 closes exactly: +81 = `test_regression_run.sh` 110 → 191**, no other suite moved. Baseline live on the real tree: `--only regression_run --save-baseline` → record written; the same suite again → *"no movement; 1 of 1 shared suite(s) unchanged"*, closure OK; `--only ts_ordering --baseline` against that record → `added test_ts_ordering.php`, `removed test_regression_run.sh`, **`totals delta -184` = `attributed -184`** (−191 + 7), `CLOSURE OK`, exit 0.
---
1929:**JSON**: the top-level key set is pinned by the suite — `tool, timestamp, repo_root, requested_sha, sha_resolved, error, files_total, linted, skipped, failures, errors, go_compile, changelog_version, ok, exit_code` (`rc == exit_code`). `go_compile` = `{attempted, go_mod, files, ok, reason, summary, duration_s}` with `reason` ∈ `no_go_module | no_go_files | go_tool_failed` (null on success); `changelog_version` = `{attempted, path, entries, unique, unparseable, duplicates, ok, reason, summary}` with `reason` ∈ `no_changelog | changelog_unreadable` (null when the file was read), `duplicates` an ordered list of the repeated version tokens and `ok` `null` when nothing was checked; `die()` emits `go_compile: null` **and** `changelog_version: null` so both keys are always present. **`[0.4.61]` closed the one open exception**: the import-time epilog refusal used to emit nothing under `--format json` (stderr only, exit 3) — empty stdin for a `| jq` consumer — and now routes through `die()`, so **every** refusal and every cannot-verify path yields the same 15-key object with `ok false`. **`[0.4.62]` closed the last one**: a usage error was the sole remaining path with no object (`--format json --timeout abc` → exit 2, usage on stderr, **empty stdout**, so a consumer met *jq's* parse error instead of our diagnosis), and it now routes through `die()` too, with **`exit_code: 2`** — the number is what keeps "you typed a bad flag" distinct from "this could not be verified" (3), so a consumer never retries a typo. **Every `--format json` run now yields exactly one 15-key object**, whatever went wrong: verdict (0/1), refusal or environment failure (3), bad argv (2); suite S6 pins the key set of the usage object against a normal run's byte for byte. The format that decides the channel is read by **argparse** from a parser carrying *only* `--format` (`_format_of`), so the read survives the very `--timeout` that broke the parse — a full pre-parse would abort first and answer `human` whenever the failing token came first (S9). Where **no** format can be read at all — an invalid choice, a bare `--format`, anything after `--` — the channel stays human, asserted in S13–S15 rather than assumed; and `--help` still prints argparse's help on stdout with exit 0, a success path where `error()` is never called (queued as its own decision).
1940:- `bash tests/test_repo_lint.sh` → **291 assertions**, hermetic (throwaway git repos under `${TMPDIR:-/tmp}/opencode`, trap-cleaned): clean→0 with exact per-language/skip counts (`files_total = linted + skipped`), all five languages caught with path+line, the 219fd8f incident replayed **both directions** (bad worktree/clean commit → 0; clean worktree/bad commit → 1), arg/env exit contracts, exact JSON key set (now including `changelog_version`), symlink invocation (sandbox + real `/data/tools`), missing-binary→3 and hung-linter→3, read-only status proof on the live repo — plus **section R**, the epilog refusal and its machine-readable form, and **section S**, the usage error's.
1949:- Live: HEAD → exit 0 (176 files — **174 → 176 with `[0.4.65]`**, its own `tools/regression-run` + `tests/test_regression_run.sh` going from untracked to tracked (`linted.bash` 24 → 25, `linted.python` 11 → 12), following **173 → 174 with `[0.4.64]`**; the pre-commit run reads **174** with `bash 24` / `python 11` because those blobs are not there yet — the stale-live-number class, re-read after the commit below — 45 module files compile clean in ~1.6s) with `changelog_version` = `attempted true, entries 70, unique 70, unparseable 0, duplicates [], ok true` (**69 → 70 with `[0.4.65]`**, following **68 → 69 with `[0.4.64]`**: the count is read from the COMMITTED blob, so it is re-read after the append commits rather than carried over — it read 68 while `[0.4.64]` was still uncommitted, which is the whole point of re-reading; the working-tree count was verified the anchored way, `grep -c '^## \['` → 69 with `uniq -d` empty, because the greedy `sed 's/.*\[\([^]]*\)\].*/\1/'` form reports **5 phantom duplicates** on this tree — 0.4.31/0.4.40/0.4.44/0.4.49/0.4.62 — by taking the last bracket in a heading that cites other versions. The **file** count is 173, not the 171 drafted mid-run: `[0.4.63]`'s own two mailbox files were untracked when that number was written and tracked once it committed, so 171 → 173 is this entry's own movement rather than a count carried); `--sha 219fd8f` → exit 1 naming `tests/test_mailbox_a11y.php:208 [php]`; `--sha 395b9b5` → exit 1 naming `.../status_test.go:5 [go-compile]`; unknown rev / non-repo → exit 3; `--sha probe-r9` → exit 3 with `requested_sha: probe-r9` (the R16 parity reference, live); `--format yaml` → exit 2 with **usage on stderr and an empty stdout** (no format could be read, so none is invented — `[0.4.62]`); **`--format json` + a bad argv → exit 2 with a 384-byte 15-key object on stdout and 0 bytes on stderr** (`--timeout abc`, `--nonsense`, `--timeout 0`, empty `--sha`, and the failing token placed *before* `--format`), while the same argv in human mode still gives argparse's `usage:`+`error:` with **stdout 0 bytes**; `--help` (**106 lines**, was 57 before the epilog stopped carrying its own prose, 102 before the exit-3 clause widened, 103 through `[0.4.61]`, **+3 for `[0.4.62]`'s widened exit-2 clause**) renders all seven docstring blocks verbatim under their labels; a copy with a heading renamed → `repo-lint: ERROR refusing to run - docstring section missing or empty …` + ` 'env:' -> 'Environment:'` → **exit 3**, stderr only, and under `--format json` the **same refusal as an object** — `ok false`, `exit_code 3`, `error` naming the pair, **stderr 0 bytes** — which is the `| jq` consumer `[0.4.60]`'s queue described, now parsing (`--format=json` → 3; `--form json` → 3; `-- --format json` → no JSON on stdout; `--format yaml` on the broken copy → refusal only, no `usage:` leak) — **and on that same broken copy `--format json --timeout abc` now yields the 458-byte refusal object with `exit_code 3` instead of falling back to `human`**, which is `[0.4.61]`'s own queue item struck as actioned.
> build · mimo-v2.6-flash-free
[0m$ [0mcd /data/repo && grep -n "^## \[0.4.65\]" CHANGELOG.md && sed -n "$(grep -n '^## \[0.4.65\]' CHANGELOG.md | cut -d: -f1),\$p" CHANGELOG.md | tail -40
1872:## [0.4.65] - 2026-09-26 — the regression total was produced by an uncommitted parser that guessed ONE summary format and reported `1854 passed / 0 failed` while listing 16 of 39 suites (twelve of them rc=0) as no-summary failures: `tools/regression-run` commits that parser, reads numbers rather than a format, and refuses to count an unparseable suite as green; suite 0 → 110, total 39/3203 → 40/3313
2. **The suite passed help *text* where `help_section` wanted a file**, so `awk` opened a paragraph as a filename and every A5 byte-comparison silently compared against nothing — a green-looking check that measured nothing. Now reads stdin.
3. **`jget` used `print(d)`**, so a dict field came back as Python's `{'fence': 1}` and a bool as `True` — not JSON. Now `str` raw, everything else `json.dumps`, so booleans read `true`/`false` (C9/C11/C12/D5/D8 assert the JSON spelling).
4. **The recursion guard was self-matching**: its own needle text sat in the assertion line it was scanning, so it could only ever fail. Replaced by extraction *by function* (`helper_line`) — the guard can no longer see itself.
5. **The G section was path-dependent**: `default_repo()` resolves relative to the script, so every mutant copied to `/tmp` reported `repo=/tmp/opencode`, found no suites and exited 5 — four reds per mutation that were **harness artifacts, not catches**. Fixed with `GLADEX_REPO_DIR` for the behavioral checks, while the path resolution itself is pinned separately (G6, shipped-binary only).
6. **The mutation driver's precondition used `grep -F` on a multi-line needle**, which `grep -F` splits into one pattern per line — it reported 3 occurrences of a genuinely unique needle and declared m3 unplantable. Counted in python instead.
Also hardened at the source: a status carrying no summary must not deposit a `None` key into `shapes`, because `sorted(shapes.items())` in the human report would raise on it.
### Mutations (6, each on a copy under `/tmp` — `tools/regression-run` md5 `1d8a556fdd762d7e5c55f0c1f8e36da9` identical before and after)
- **M1** `no_summary` removed from the exit precedence → caught by **2** (B9 `silent -> 4 (want=4 got=0)`, B23 precedence `want=4 got=1`).
- **M2** the tie-break defeated (`preferred = list(matches)`) → caught by **4** (C1 `want=12 got=999`, C2, C4, C5).
- **M3** a `no_summary` suite counted in `totals.suites_run` → caught by **2** (B12b `want=0 got=1`, B26 `want=4 got=5`). This is the original harm, one level down in the totals instead of the exit code.
- **M4** crash detection defeated (`elif rec["rc"] != 0:` → `elif False:`) → caught by **3** (B14, B15, B16).
- **M5** the epilog refusal defeated → caught by **6** (F1–F6, including `--help` producing 2270 bytes where 0 were allowed).
- **M6** the pre-parser's stderr suppression dropped → caught by **2** (E10, E11 `want=1 got=3`).
Each mutant's reds are **confined to its own section** — the M-fix above is what made that true; before it, every mutant also lit up G1–G4 for path reasons. `m3`'s needle was first declared UNPLANTABLE by its own precondition (the same condition text appears in the human-report formatter); the needle was widened to two lines rather than the precondition relaxed.
### Test suite
New **`tests/test_regression_run.sh` → 110 assertions**, hermetic (throwaway fixtures under `/tmp/opencode/trr.*`, trap-cleaned): the `--help`/docstring byte-comparison for all four sections, the full exit-code contract (`0/1/2/3/4/5`) with precedence, all four parsing rules, the 13-key JSON contract including the usage-error and refusal objects, the flag contract (including "stderr is exactly one line"), the epilog refusal in both channels, live read-only proof, and the recursion guard. Test hook `REGRESSION_RUN_BIN` points the suite at a mutant copy.
### Docs
- `tools/REGISTRY.md`: **new `## regression-run` and `## test_regression_run.sh` sections**; §repo-lint `**Status**` line moved **39 / 3203 / 19 shell = 2012 → 40 / 3313 / 20 shell = 2122** (20 PHP = 1191 unchanged); Live bullet's `files_total` **174 → 176** and `changelog_version` **69 → 70**, both written as *pre-commit predictions* and re-read after the commit rather than carried.
- `CHANGELOG.md`: `[0.4.64]`'s queue item (1) struck and marked actioned; **`[0.4.65]` appended at the bottom** with the placement note (train head deliberately still `0.4.28`).
- **No new gate, no new dashboard check** → `system-status`'s check count untouched.
### Live (no restart, no deploy, no promote)
`./tools/regression-run` → **40 suites, 3313 passed, 0 failed, 0 skipped**, exit 0. `tools/repo-lint` → exit 0, `files_total 174`, `linted {bash 24, go 43, json 11, php 32, python 11}`, `changelog_version entries 69, unique 69, duplicates []` (all pre-commit). `tools/source-sync-check` → in sync, 42 files, exit 0. `tools/system-status --format human` → **ALL SYSTEMS HEALTHY**.
### Queue (next candidates, not actioned)
- **New, from this step**: `regression-run` has no `--jobs` **by design**, so a full run costs ~4m24s wall; if that ever becomes a problem, the constraint to solve is *which* suites may run concurrently, not a flag.
- **Carried from `[0.4.64]` (2)**: `--format json --help` prints human help on stdout and exits 0.
- **Carried from `[0.4.64]` (3)**: `tests/test_repo_lint.sh`'s header mutation index is a second copy of the mutation list and will drift again.
- **Carried from `[0.4.54]` (2)**: `ship-tree` names the first offending path but not *why* it is there.
- **Carried from `[0.4.54]` (3)**: `--ignored` may need a pathspec exclude if `app/src/php` gains a large ignored build dir.
- **Carried from `[0.4.54]` (4)**: the `promote-gates` age re-ages on every run.
- **Carried (investor-owned)**: NEEDS-INVESTOR **#57 public investor-route gating A/B/C** and **SOA MNAME**.
### Placement (deliberate — same as `[0.4.29]`–`[0.4.64]` which sit immediately above this one)
Parked at the bottom of an otherwise newest-first file on purpose: promoting it to the top would make it the version-train head while `GLADEX_APP_VERSION` and all four `gladex` binaries still report `0.4.28`, turning `tests/test_app_version` and `tests/test_cli_version` red until CI bumps the train. That bump is main-loop work and is not part of this fix.
> build · mimo-v2.6-flash-free
[0m$ [0mcd /data/repo && grep -n "baseline" CHANGELOG.md | head -20; echo "--- queue lines in 0.4.65 ---"; awk '/^## \[0.4.65\]/{f=1} f&&/^### Queue/,f&&/^### Placement/' CHANGELOG.md | head -20
947:- **Full regression after landing: 32 suites, 1756 assertions, 0 failed** (17 shell = 1275: budget 49, dns 40, domain 140, identity 28, ip-drift-check 133, ip-drift-cron 93, pdns 175, promote 67, **promote-lint 60**, repo-lint 120, smtp 38, source-sync 30, go-compile 82, go-tests 66, mx_soa 31, unread 24, tls 99 + 15 PHP = 481) — **+60 over the 1,696 baseline**, no other suite moved. `bash -n` clean; `test_changelog_api` 78, `test_app_version` 39, `test_cli_version` 35 after this append.
990:- **Full regression: 34 suites, 1960 assertions, 0 failed** (19 shell = 1479: budget 49, dns 40, domain 140, identity 28, ip-drift-check 133, ip-drift-cron 93, pdns 175, promote 67, promote-json 84, promote-lint 60, repo-lint 120, smtp 38, source-sync 30, go-compile 82, go-tests 66, mx_soa 31, **promote-gates 120**, unread 24, tls 99 + 15 PHP = 481) — **+36 over the 1,924 baseline**, no other suite moved. `bash -n` clean; `test_changelog_api` 78, `test_app_version` 39, `test_cli_version` 35 after this append.
1013:- **Full regression: 34 suites, 2019 assertions, 0 failed** (19 shell = 1538: budget 49, dns 40, domain 140, identity 28, ip-drift-check 133, ip-drift-cron 93, pdns 175, promote 67, **promote-json 108**, promote-lint 60, repo-lint 120, smtp 38, source-sync 30, go-compile 82, go-tests 66, mx_soa 31, **promote-gates 155**, unread 24, tls 99 + 15 PHP = 481) — **+59 over last run's 1,960 baseline** (= +24 + +35, exactly the two suites that grew), no other suite moved. `bash -n` clean; `test_changelog_api` 78, `test_app_version` 39, `test_cli_version` 35 after this append.
1035:- **Full regression: 34 suites, 2029 assertions, 0 failed** (19 shell = 1548: budget 49, dns 40, domain 140, identity 28, ip-drift-check 133, ip-drift-cron 93, pdns 175, promote 67, **promote-json 118**, promote-lint 60, repo-lint 120, smtp 38, source-sync 30, go-compile 82, go-tests 66, mx_soa 31, **promote-gates 155**, unread 24, tls 99 + 15 PHP = 481) — **+10 over the 2,019 baseline** (= exactly promote-json's growth), no other suite moved. `bash -n` + `py_compile` clean; `test_changelog_api` 78, `test_app_version` 39, `test_cli_version` 35 after this append. (A first regression pass reported `go-compile 81 passed` with a non-zero exit while a live `system-status` overlapped it; three standalone re-runs and the clean second pass were 82/0 — load contention from that overlap, not this change.)
1059:- **Full regression: 34 suites, 2059 assertions, 0 failed** (19 shell = 1578 — **+30** over the 2,029 baseline, exactly promote-json's +9 and promote-gates's +21 — plus 15 PHP = 481, unmoved); `test_changelog_api` 78/0, `test_app_version` 39/0, `test_cli_version` 35/0 all run against this entry.
1079:- **Full regression: 34 suites, 2114 assertions, 0 failed** (19 shell = 1633 — **+55** over the 2,059 baseline, exactly promote-gates's growth — plus 15 PHP = 481, unmoved); `test_changelog_api` 78/0, `test_app_version` 39/0, `test_cli_version` 35/0 all run against this entry. Code committed in `cb29987` (a concurrent shift swept this run's two working-tree files into its commit); this commit carries CHANGELOG + PROGRESS + REGISTRY.
1400:- Assertions written **before** the tool was touched; pre-fix red captured (tool md5 `3911bdccaa03846ee4d8bba55801e94a`, suite md5 `8573a43b48a5b4a71460cb53895582ff`, log `/tmp/opencode/helpcontract/pre-fix.log`) → **130 passed / 9 failed**: **8 behavioural** (the epilog's three rules, the `tools/repo-lint --help` pointer, the stale parse+compile sentence still present, the docstring's missing section, that rule absent there too, and the 0b → 0c → 0d position) plus **M13 unplantable** (`plant matched 0 line(s)` — the line it would revert did not exist yet). The **guards passed pre-fix**, which is what identifies them as guards: the entire 129-assertion baseline, and `Commit lint gate (exit 7) — see below.` — the promise, present while its target was not.
1841:`php tests/test_app_contrast.php` → **185 passed, 0 failed, 0 skipped**, exit 0 (identical to baseline). `php tests/test_app_contrast_meta.php` → **48 passed, 0 failed, 0 skipped**, exit 0, `suite under test: /data/repo/tests/test_app_contrast.php`, temp trees under `/tmp/opencode/contrast-meta-*` registered for shutdown-cleanup. Both `php -l` clean. `tools/system-status --format human` → **ALL SYSTEMS HEALTHY**, exit 0 (standing warnings only: SOA `mname=placeholder` NEEDS-INVESTOR, `investor-messages [OK] 0 unread dev=0 prod=0`).
--- queue lines in 0.4.65 ---
### Queue (next candidates, not actioned)
- **New, from this step**: `regression-run` has no `--jobs` **by design**, so a full run costs ~4m24s wall; if that ever becomes a problem, the constraint to solve is *which* suites may run concurrently, not a flag.
- **Carried from `[0.4.64]` (2)**: `--format json --help` prints human help on stdout and exits 0.
- **Carried from `[0.4.64]` (3)**: `tests/test_repo_lint.sh`'s header mutation index is a second copy of the mutation list and will drift again.
- **Carried from `[0.4.54]` (2)**: `ship-tree` names the first offending path but not *why* it is there.
- **Carried from `[0.4.54]` (3)**: `--ignored` may need a pathspec exclude if `app/src/php` gains a large ignored build dir.
- **Carried from `[0.4.54]` (4)**: the `promote-gates` age re-ages on every run.
- **Carried (investor-owned)**: NEEDS-INVESTOR **#57 public investor-route gating A/B/C** and **SOA MNAME**.
### Placement (deliberate — same as `[0.4.29]`–`[0.4.64]` which sit immediately above this one)
> build · mimo-v2.6-flash-free
[0m$ [0mcd /data/repo && grep -n "which assertions\|per-suite assertion counts\|mechanically instead\|attribution" CHANGELOG.md agent-logs/PROGRESS.md | head -20
CHANGELOG.md:1348:The old rule was not arbitrary: *one message cannot honestly describe two failures*, and a bare reason dropped after a list of several files could be read as blaming the wrong file. **Choice made, not asked**: keep that guarantee by **attribution** instead of by silence — quote a `path: message` pair per failure, so no message can ever stand for two. Nothing was slipped in: this is the queued item, landed as its own step with its own tests.
CHANGELOG.md:1352:- each message is whitespace-collapsed first (that is what keeps the whole `detail` **single-line** — it is re-embedded in JSON and rendered with `grep -o`) and capped at 200 chars; the **path is never truncated**, so attribution cannot be cut in half;
CHANGELOG.md:1358:- Assertions written **before** the tool was touched; pre-fix red captured (tool md5 `e3a691677b8f419ff11f36130bbf202e`, log `/tmp/opencode/lintreason/pre-fix.log`, blob kept at `/tmp/opencode/lintreason/pre-fix-promote`) → **109 passed / 11 failed**: **8 behavioural** (N2's two attributions, N3 ×6) plus **M10–M12 unplantable** (`plant matched 0 line(s)` — the code they target does not exist yet).
CHANGELOG.md:1359:- **One guard was replaced deliberately, and it is recorded here because replacing a guard is exactly how a regression slips in**: N2's `assert_not_contains "no single message stands for two failures" "duplicate version"` pinned the *old* limitation — silence — and the queue item asks for that silence to end. Its successor is stronger, not weaker: two `assert_contains` on the **full `path: message` strings** (which only pass if the reason is glued to its own file) plus two `assert_not_contains` on the **unattributed shape** (` [duplicate version`, ` [PHP Parse error` — a bracket opened by a bare message). Silence is no longer the property being tested; attribution is.
CHANGELOG.md:1536:- **M13** — the inverse: `("env:", "Environment:")` repointed to the languages heading, so `env:` renders a different docstring block. Caught by **Q-render alone**: **Q-source is asserted GREEN** (the `Environment:` text still occurs exactly once, so a count cannot see a misattribution), and the `exit codes:` pair still agrees on the mutant (one pair wrong, not the whole epilog).
agent-logs/PROGRESS.md:4801:- **Docs**: REGISTRY §repo-lint rewritten (usage incl. `--go-timeout`, the four exit codes with their evidence rules, the attribution rule, the JSON `go_compile` object, both env hooks, 120-assertion suite + pre-fix replay + live transcript); CHANGELOG `[0.4.37]` parked at the **bottom** with the placement note (train head stays `0.4.28` → version tests stay green); PROGRESS (this entry).
agent-logs/PROGRESS.md:5019:- **The old rule was right and the fix keeps it — by attribution, not by silence**: one message genuinely cannot describe two failures, and a bare reason dropped after a list of files could be read as blaming the wrong one. `_lint_failure_messages` now returns `"; "`-joined **`path: message`** pairs — **one format for a single failure and for many alike** (the single case gained its `path:` prefix too, so no branch quotes an unattributed reason). Each message is whitespace-collapsed first (the guarantee that keeps `detail` single-line for JSON re-embedding and `grep -o`) and capped at 200 chars, while the **path is never truncated** — attribution cannot be cut in half. Bounded exactly like `_lint_failure_paths`: **first 5 failures then `(+N more)`**, so both halves of the line stop at the same place and neither can grow without a trace. **A failure that reported no message gets NO pair** — a missing reason is honest, an invented one is not — and its file still shows in the path list. The bracket still exists only on the non-syntax branch, so `[0.4.53]`'s three shapes are untouched: a real syntax failure keeps `fail to parse or compile`, an empty `failures[]` claims no cause.
agent-logs/PROGRESS.md:5020:- **One guard was REPLACED, recorded deliberately**: N2's `assert_not_contains "no single message stands for two failures" "duplicate version"` pinned the *old* limitation — silence — which is exactly what the queue item asks to end. Left in place it would have kept the fix red forever; deleted without a successor it would have removed the only check on the property it was protecting. Its successor is stronger: two `assert_contains` on the **full `path: message` strings** (they pass only if each reason is glued to its own file) plus two `assert_not_contains` on the **unattributed shape** (` [duplicate version`, ` [PHP Parse error` — a bracket opened by a bare message). The property under test changed from *silence* to *attribution*.
agent-logs/PROGRESS.md:5080:- **Mutations (2 new, on copies — the real tool is never edited; precondition-asserted exactly once)**: **M12** a second copy of a *derived* section's text planted where `--help` never renders it (a source comment carrying the go-gate line) — caught by **Q-source alone**, with **Q-render asserted GREEN** on the mutant (`--help` still equals the docstring: the copy prints nothing, so the byte comparison provably cannot see it) and `--help` still exiting 0. **M13** the inverse — `("env:", "Environment:")` repointed to the languages heading so `env:` renders a different block — caught by **Q-render alone**, with **Q-source asserted GREEN** (the `Environment:` text still occurs exactly once; a count cannot see a misattribution) and the `exit codes:` pair still agreeing on the mutant. **11 → 13**; complementary by construction exactly as M10/M11 were — derivation removes the *drift* risk, not the *duplication* risk.
agent-logs/PROGRESS.md:5203:- **Next-candidate queued, not actioned**: (1) **New, from this step** — `regression-run` reports *counts*, not *which* assertions moved; a `--baseline FILE` mode that diffs two runs' per-suite assertion counts would turn "+110 somewhere" into "+110 in `test_regression_run.sh`" mechanically instead of by arithmetic done by hand above. (2) Carried from `[0.4.64]` (2): `--format json --help` prints **human** help on stdout and exits **0**. (3) Carried from `[0.4.64]` (3): `tests/test_repo_lint.sh`'s header mutation index is a second copy of the mutation list and will drift again — derive or drop it. (4) Carried from `[0.4.54]` (2): `ship-tree` names the first offending path but not *why*. (5) Carried from `[0.4.54]` (3): `--ignored` pathspec exclude. (6) Carried from `[0.4.54]` (4): the `promote-gates` age re-ages on every run (intended).
> build · mimo-v2.6-flash-free
Before writing docs, one gap I should close: the baseline file carries a `format` field that nothing validates — a field that exists but is never checked is exactly the vacuous pattern this log keeps recording. Let me fix it:
[0m← [0mEdit repo/tools/regression-run
Index: repo/tools/regression-run
===================================================================
--- repo/tools/regression-run
+++ repo/tools/regression-run
@@ -493,8 +493,17 @@
"document — write one with --save-baseline"
)
if kind != BASELINE_KIND:
raise _UsageError(f"baseline {path} is not a baseline document (kind={kind!r})")
+ # A field that exists but is never read is a field that lies. `format` was
+ # written into every record from the first version; until now nothing
+ # checked it, so a future layout change would have been read as this one.
+ fmt = doc.get("format")
+ if fmt != BASELINE_FORMAT:
+ raise _UsageError(
+ f"baseline {path} is in format {fmt!r}, this tool reads "
+ f"{BASELINE_FORMAT} — write it again with --save-baseline"
+ )
if not isinstance(doc.get("totals"), dict):
raise _UsageError(f"baseline {path} has no totals object")
suites = doc.get("suites")
if not isinstance(suites, list):
[0m← [0mEdit repo/tests/test_regression_run.sh
Index: repo/tests/test_regression_run.sh
===================================================================
--- repo/tests/test_regression_run.sh
+++ repo/tests/test_regression_run.sh
@@ -541,8 +541,19 @@
run_sandbox green --baseline "$SB/dupe.json"
assert_eq "I62 a suite listed twice in the baseline -> 2" 2 "$RC"
assert_contains "I62b the refusal names the duplicated suite" "twice" "$ERR"
+python3 -c '
+import json, sys
+d = json.load(open(sys.argv[1]))
+d["format"] = 99
+json.dump(d, open(sys.argv[2], "w"))
+' "$SB/base1.json" "$SB/fmt99.json"
+run_sandbox green --baseline "$SB/fmt99.json"
+assert_eq "I62c an unreadable baseline format -> 2" 2 "$RC"
+assert_contains "I62d both formats named (file 99, reader 1)" \
+ "format 99, this tool reads 1" "$ERR"
+
run_sandbox green --list --baseline "$SB/base1.json"
assert_eq "I63 --list + --baseline -> 2" 2 "$RC"
assert_contains "I64 --list conflict explained" "conflicting flags" "$ERR"
run_sandbox green --list --save-baseline "$SB/listsave.json"
> build · mimo-v2.6-flash-free
[0m← [0mEdit /tmp/opencode/mutate_baseline.sh
Index: /tmp/opencode/mutate_baseline.sh
===================================================================
--- /tmp/opencode/mutate_baseline.sh
+++ /tmp/opencode/mutate_baseline.sh
@@ -73,6 +73,17 @@
if b is not None else contrib({\"status\": None}))"""
s = s.replace(old, new)
'
+run_mutant M7-format-never-checked '
+old = """ fmt = doc.get(\"format\")
+ if fmt != BASELINE_FORMAT:
+ raise _UsageError(
+ f\"baseline {path} is in format {fmt!r}, this tool reads \"
+ f\"{BASELINE_FORMAT} — write it again with --save-baseline\"
+ )"""
+assert s.count(old) == 1, "anchor M7"
+s = s.replace(old, " pass")
+'
+
NOW_MD5=$(md5sum "$REAL" | cut -d' ' -f1)
echo "real tool md5 before=$REAL_MD5 after=$NOW_MD5 $([ "$REAL_MD5" = "$NOW_MD5" ] && echo IDENTICAL || echo CHANGED!)"
> build · mimo-v2.6-flash-free
[0m$ [0mcd /data/repo && bash tests/test_regression_run.sh 2>&1 | grep -E "^(FAIL|=== Results)"; echo "---"; bash /tmp/opencode/mutate_baseline.sh 2>&1 | tail -20
=== Results: 193 passed, 0 failed ===
---
M1-closure-always-true rc=1 === Results: 186 passed, 3 failed ===
red: FAIL: I32 mismatch closure_ok false FAIL: I36 CLOSURE MISMATCH printed FAIL: I37 mismatch explained
M2-exit-code-flips rc=1 === Results: 185 passed, 4 failed ===
red: FAIL: I18 a run that LOST assertions still exits 0 FAIL: I33 mismatch does not change the exit code FAIL: I44 status-change baseline -> 0 FAIL: I69 --baseline with --save-baseline -> 0
M3-accept-any-json rc=1 === Results: 185 passed, 4 failed ===
red: FAIL: I60 a run payload is not a baseline -> 2 FAIL: I61 the refusal names the fix FAIL: I62c an unreadable baseline format -> 2 FAIL: I62d both formats named
M4-loss-not-reported rc=1 === Results: 186 passed, 3 failed ===
red: FAIL: I27 the loss names gamma FAIL: I28 loss records before/after FAIL: I30 human line for the loss
M5-added-removed-dropped rc=1 === Results: 187 passed, 2 failed ===
red: FAIL: I38 added is the suite the baseline dropped FAIL: I39 removed is the suite the run lacks
M6-asymmetric-contrib rc=1 === Results: 188 passed, 1 failed ===
red: FAIL: I49 closure ok
M7-format-never-checked rc=1 === Results: 187 passed, 2 failed ===
red: FAIL: I62c an unreadable baseline format -> 2 FAIL: I62d both formats named
real tool md5 before=cd6b13e23777853c85773f803652958a after=cd6b13e23777853c85773f803652958a IDENTICAL
> build · mimo-v2.6-flash-free
193/193, all 7 mutations caught. Now the final full regression with the finished code:
Select another run log from the list above. Only files matching run-YYYYMMDD-HHMMSS-N.log are readable.
App log tail — prod-8001.log (last 60 lines)
[Sat Sep 26 04:46:22 2026] 127.0.0.1:33938 Accepted [Sat Sep 26 04:46:22 2026] 127.0.0.1:33938 Closing [Sat Sep 26 04:46:22 2026] 127.0.0.1:33944 Accepted [Sat Sep 26 04:46:22 2026] 127.0.0.1:33944 Closing [Sat Sep 26 04:46:22 2026] 127.0.0.1:33946 Accepted [Sat Sep 26 04:46:22 2026] 127.0.0.1:33946 Closing [Sat Sep 26 04:50:11 2026] 127.0.0.1:52174 Accepted [Sat Sep 26 04:50:11 2026] 127.0.0.1:52174 Closing [Sat Sep 26 04:56:57 2026] 127.0.0.1:41682 Accepted [Sat Sep 26 04:56:57 2026] 127.0.0.1:41682 Closing [Sat Sep 26 04:56:57 2026] 127.0.0.1:41694 Accepted [Sat Sep 26 04:56:57 2026] 127.0.0.1:41694 Closing [Sat Sep 26 04:56:57 2026] 127.0.0.1:41704 Accepted [Sat Sep 26 04:56:57 2026] 127.0.0.1:41704 Closing [Sat Sep 26 04:56:57 2026] 127.0.0.1:41712 Accepted [Sat Sep 26 04:56:57 2026] 127.0.0.1:41712 Closing [Sat Sep 26 04:57:13 2026] 127.0.0.1:50374 Accepted [Sat Sep 26 04:57:13 2026] 127.0.0.1:50374 Closing [Sat Sep 26 04:57:13 2026] 127.0.0.1:50388 Accepted [Sat Sep 26 04:57:13 2026] 127.0.0.1:50388 Closing [Sat Sep 26 04:57:13 2026] 127.0.0.1:50396 Accepted [Sat Sep 26 04:57:13 2026] 127.0.0.1:50396 Closing [Sat Sep 26 04:57:13 2026] 127.0.0.1:50398 Accepted [Sat Sep 26 04:57:13 2026] 127.0.0.1:50398 Closing [Sat Sep 26 04:57:13 2026] 127.0.0.1:50400 Accepted [Sat Sep 26 04:57:13 2026] 127.0.0.1:50400 Closing [Sat Sep 26 04:57:28 2026] 127.0.0.1:53564 Accepted [Sat Sep 26 04:57:28 2026] 127.0.0.1:53564 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53574 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53574 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53582 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53582 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53586 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53586 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53602 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53602 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53604 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53604 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53608 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53608 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53618 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53618 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53626 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53626 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53642 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53642 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53656 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53656 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53664 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53664 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53666 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53666 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53682 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53682 Closing [Sat Sep 26 04:57:29 2026] 127.0.0.1:53692 Accepted [Sat Sep 26 04:57:29 2026] 127.0.0.1:53692 Closing [Sat Sep 26 04:59:39 2026] 127.0.0.1:35946 Accepted [Sat Sep 26 04:59:39 2026] 127.0.0.1:35946 Closing [Sat Sep 26 04:59:39 2026] 127.0.0.1:35952 Accepted
Generated 2026-09-26 02:59:39 UTC · Gladex.de