Gladex Agent Logs

Agent run logs & app logs · env: prod · LAN-only investor surface

Overview
Run logs960 files, 50.8 MB
Latest run logrun-20261003-031958-558.log
Log directory/data/agent-logs
App log directory/opt/startup/prod/logs
Run logs (newest first, last 50)
FileSizeModified (UTC)
run-20261003-031958-558.log 103 KB 2026-10-03 01:21:39
run-20261003-024901-557.log 376 KB 2026-10-03 01:09:51
run-20261003-020542-556.log 233 KB 2026-10-03 00:38:54
run-20261003-015534-555.log 153 B 2026-10-02 23:55:35
run-20261003-014525-554.log 153 B 2026-10-02 23:45:26
run-20261003-013518-553.log 153 B 2026-10-02 23:35:18
run-20261003-012510-552.log 153 B 2026-10-02 23:25:11
run-20261003-011502-551.log 153 B 2026-10-02 23:15:02
run-20261003-010454-550.log 190 B 2026-10-02 23:04:55
run-20261003-005446-549.log 153 B 2026-10-02 22:54:47
run-20261003-004439-548.log 153 B 2026-10-02 22:44:39
run-20261003-003431-547.log 153 B 2026-10-02 22:34:32
run-20261003-002423-546.log 153 B 2026-10-02 22:24:24
run-20261003-001415-545.log 153 B 2026-10-02 22:14:16
run-20261003-000408-544.log 153 B 2026-10-02 22:04:08
run-20261002-235400-543.log 153 B 2026-10-02 21:54:01
run-20261002-234352-542.log 153 B 2026-10-02 21:43:53
run-20261002-233344-541.log 153 B 2026-10-02 21:33:45
run-20261002-232337-540.log 190 B 2026-10-02 21:23:37
run-20261002-231329-539.log 153 B 2026-10-02 21:13:30
run-20261002-230321-538.log 153 B 2026-10-02 21:03:22
run-20261002-225313-537.log 153 B 2026-10-02 20:53:14
run-20261002-224306-536.log 153 B 2026-10-02 20:43:06
run-20261002-223258-535.log 153 B 2026-10-02 20:32:59
run-20261002-222250-534.log 190 B 2026-10-02 20:22:51
run-20261002-221242-533.log 153 B 2026-10-02 20:12:43
run-20261002-220235-532.log 153 B 2026-10-02 20:02:35
run-20261002-211002-531.log 371 KB 2026-10-02 19:52:28
run-20261002-200155-530.log 366 KB 2026-10-02 18:59:55
run-20261002-185533-529.log 349 KB 2026-10-02 17:51:48
run-20261002-170315-528.log 651 KB 2026-10-02 16:45:25
run-20261002-161229-527.log 357 KB 2026-10-02 14:53:08
run-20261002-160222-526.log 153 B 2026-10-02 14:02:23
run-20261002-155214-525.log 153 B 2026-10-02 13:52:15
run-20261002-154207-524.log 153 B 2026-10-02 13:42:08
run-20261002-153159-523.log 190 B 2026-10-02 13:32:00
run-20261002-152152-522.log 153 B 2026-10-02 13:21:53
run-20261002-151144-521.log 153 B 2026-10-02 13:11:45
run-20261002-150137-520.log 153 B 2026-10-02 13:01:38
run-20261002-145129-519.log 153 B 2026-10-02 12:51:30
run-20261002-144121-518.log 190 B 2026-10-02 12:41:22
run-20261002-143114-517.log 190 B 2026-10-02 12:31:15
run-20261002-142106-516.log 153 B 2026-10-02 12:21:07
run-20261002-141059-515.log 153 B 2026-10-02 12:10:59
run-20261002-140051-514.log 153 B 2026-10-02 12:00:52
run-20261002-135044-513.log 153 B 2026-10-02 11:50:45
run-20261002-134037-512.log 153 B 2026-10-02 11:40:37
run-20261002-133028-511.log 153 B 2026-10-02 11:30:29
run-20261002-132021-510.log 153 B 2026-10-02 11:20:21
run-20261002-131012-509.log 190 B 2026-10-02 11:10:13
Tail — run-20261003-031958-558.log (last 200 lines)
- **Suites re-run on the pushed tree: `bash tests/test_registry_coverage.sh` → 280 / 0 (third run, twice after the `REGISTRY.md` edits); `bash tests/test_repo_lint.sh` → 463 / 0; `php tests/test_changelog_api.php` → 86 / 0 (reference parse **180**); `php tests/test_changelog_mobile.php` → 125 / 0 / 0; `bash .githooks/pre-commit` → exit 0.** `tools/system-status` → **exit 0, `Overall: ALL SYSTEMS HEALTHY`**, `git-tree [OK] clean`, `queue-source [OK] one queue: [0.4.175] pointer-only … 134 PROGRESS bullet(s), 2 path token(s)`, `investor-messages [OK] 0 unread dev=0 prod=0`, `investor-duty [OK] owed=0 unread=0 unreplied=0 open=0`, `cloud [OK] nextcloud 200 installed=true / immich 200 pong; 5/5 containers up`, all seven `DNS:` plus `MX:gladex.de` **[OK]** (read-only lookups, **no write**), `tls-cert-expiry [OK] 80d` / `-photos [OK] 83d`, `go-compile [OK] 45 module file(s) compile clean (1.715s) (commit 571aa0c)`, `go-tests [OK] passing (worktree)`, plus the **same three WARNs** this run neither touched nor worsened: `SOA … mname=placeholder (NEEDS-INVESTOR open)`, `promote-gates … verdict VERDICT-20260930T022500-team-sofia.md is STALE … re-review required | dev-sync OK | commit-lint OK | ship-tree OK (commit 571aa0c)` — the **reviewer's** to refresh, `app/src/php` untouched here so no promote was attempted — and `red-watch [WARN] red, failed=12, since 2026-10-03T01:01:38Z`, whose state file (`updated=2026-10-03T01:00:01Z`) is the cron's **snapshot of this run's own uncommitted instant** (its 12 failures are `A3`/`A4`–`A8`/`A12`/`A13`/`A15`–`A18`, i.e. a dirty tree mid-commit): warn-only by construction, the `*/15` job re-reads it after commit #1, and it is reported here rather than smoothed over. `tools/healthcheck` → **dev HEALTHY / prod HEALTHY, both HTTP 200, both 0.4.28**. `tools/budget-show` → **month 2026-10, 5.00 / 0.00 / 5.00**, `warnings []` — **0.00 spent, no paid key, no purchase, `*-free` models only**. `crontab -l` unchanged at **two lines** (weekly `ip-drift-cron`, `*/15 red-watch`).

- **STEP 0 re-read at the close — still nothing owed, so no reply was written.** `SELECT count(*) FROM messages WHERE direction='investor_to_agent' AND read=0` → **0 dev / 0 prod**; `tools/inbox-status` → **exit 0, "OK - nothing owed (0 unread, 0 open entries all replied)"**, last reply **dev 142 / prod 107**; `INBOX.md` → **79 entries / 79 handled / 0 open**. **No `agent_to_investor` row was inserted at any point in this run — the thread was never touched.**

- **CLOSE PROTOCOL — this closing section is appended to `agent-logs/PROGRESS.md` *before* commit #2, and commit #2 carries exactly `git commit -- agent-logs/PROGRESS.md` (no `git add -A`).** After that push, `git status --porcelain` must not list `agent-logs/PROGRESS.md`. Census closes **24 tools / 36 registered sections / 83 suites / `- Live: 83` / `GLADEX_APP_VERSION` 0.4.28 / spend 0.00 € of 5.00 € (month 2026-10)**, with one visible step taken — **(118)** — and **(119)** queued as the next runnable one.
$ for db in /opt/startup/dev/data/messages.db /data/prod/data/messages.db /data/dev/data/messages.db; do echo "== $db"; [ -f "$db" ] && sqlite3 "$db" ".tables" || echo missing; done; echo "---find---"; find / -name messages.db -not -path '*/proc/*' 2>/dev/null | head -20
== /opt/startup/dev/data/messages.db
messages
== /data/prod/data/messages.db
missing
== /data/dev/data/messages.db
missing
---find---
/tmp/opencode/jonas-shift-27/env/data/messages.db
/opt/startup/prod/data/messages.db
/opt/startup/dev/data/messages.db
> build · mimo-v2.6-flash-free
$ for db in /opt/startup/dev/data/messages.db /opt/startup/prod/data/messages.db; do echo "== $db"; sqlite3 "$db" "SELECT id, direction, read, ts, substr(replace(body,char(10),' '),1,300) FROM messages WHERE direction='investor_to_agent' AND read=0;"; echo "--- counts"; sqlite3 "$db" "SELECT direction, read, count(*) FROM messages GROUP BY 1,2;"; echo "--- newest"; sqlite3 "$db" "SELECT id, direction, read, ts FROM messages ORDER BY id DESC LIMIT 3;"; done
== /opt/startup/dev/data/messages.db
--- counts
agent_to_investor|0|28
agent_to_investor|1|89
investor_to_agent|1|25
--- newest
142|agent_to_investor|1|1790908202
141|agent_to_investor|1|1790745706
140|agent_to_investor|1|1790740857
== /opt/startup/prod/data/messages.db
--- counts
agent_to_investor|0|32
agent_to_investor|1|71
investor_to_agent|1|4
--- newest
107|agent_to_investor|1|1790908202
106|agent_to_investor|1|1790745706
105|agent_to_investor|1|1790740857
$ cd /data/repo && ./tools/inbox-status; echo "exit=$?"; echo "--- open entries"; grep '^## ' INBOX.md | grep -vc HANDLED; git status --porcelain; git log --oneline -3
inbox-status — STEP 0 investor duty

  messages unread : dev 0 / prod 0 / total 0
  last reply      : dev 142 / prod 107
  INBOX.md        : 79 entries, 79 handled, 0 open

verdict: OK - nothing owed (0 unread, 0 open entries all replied)
exit=0
--- open entries
0
2a89f8b closing measurements for [0.4.175] stale-number correction - repo-lint re-reads 180 entries / files_total 270 unchanged, queue-source 0 violations, 83 suites, ALL SYSTEMS HEALTHY, STEP 0 still 0/0, CLOSE PROTOCOL before this commit
571aa0c queue item (118): the [0.4.65] note's two stale numbers corrected and dated - greedy-parse phantoms 5 -> a dated 14-line measurement with the empty-string class, and the bullet's opening 176 files -> a files_total pointer (270) [0.4.175]; PROGRESS entry appended before this commit
4372ebc closing measurements for [0.4.174] red-watch dashboard row - detector re-reads on the pushed tree (monitor 27/0, system-status ALL SYSTEMS HEALTHY, red-watch row green), RECOVERED observed by the cron 6s after commit #1, CLOSE PROTOCOL before this commit
> build · mimo-v2.6-flash-free
STEP 0 checked: 0 unread investor rows (dev 0 / prod 0), INBOX 79/79 handled, `inbox-status` exit 0 — no reply owed. Tree clean. Now the queued item **(119)**.
$ cd /data/repo && grep -n '\- Live:' tools/REGISTRY.md
66:- Live: `domain-availability-check gladex.de` → exit 3 (taken); a free `.de` → exit 0; `--format human gladex.de` → `TAKEN` with all three sources listed.
132:- Live: `budget-show` → 0; `--format json` → balanced 5.00 − 0.00 = 5.00 (`month: 2026-10`, rolled 2026-10-01; September closed 1.50 / 3.50, history kept verbatim — the `spent:` note carries the open `.de` renewal question, REPORT.md §14) with `"warnings": []` while the month is current, and `GLADEX_CURRENT_MONTH=2099-12` → still exit **0** with exactly one warning; `/api/budget` on :8000/:8001 → same numbers, both pages `200` (PHP parses the ledger independently)
198:- 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
757:- Live: `promote-dev-to-prod --dry-run` → exit 5 while `reviewer-to-main/` is empty
850:- Live: `ip-drift-check --format json --domain gladex.de` → exit 0, `drift:false` (tunnel up); `--help` → 0.
1160:**Status**: Built ✅ 2026-09-29 (queue item **(82)**; the root-filesystem watchdog). Registers with the `- Live:` suite figure **61 → 62**, which is what turns `tests/test_registry_coverage.sh` section F red until that line is refreshed in the same commit that adds the suite.
2441:- 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
2492:- 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)
2975:- Live: HEAD → exit 0 (the live file count is `files_total` in `repo-lint --format json` — **270** measured 2026-10-03; this bullet's own opening figure was **176 files**, the `[0.4.65]`-era (2026-09-26) count, whose dated history follows — **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. **`[0.4.66]` pre-commit read: exit 0, `files_total 176`, `linted {bash 25, go 43, json 11, php 32, python 12}`, `changelog_version entries 70, unique 70, duplicates []`** — both changed files were already tracked, so the post-commit prediction is **176 unchanged** with `entries 71` once `[0.4.66]` lands, re-read after the commit rather than carried. **`[0.4.67]` pre-commit read: exit 0, `files_total 176`, `linted {bash 25, go 43, json 11, php 32, python 12}`, `changelog_version entries 71, unique 71, duplicates []`** — this entry adds two **untracked** files (`tools/queue-source-check`, extensionless `python3` shebang → `linted.python`; `tests/test_queue_source.sh`, `.sh` → `linted.bash`) plus its own heading, so the post-commit prediction is **178** with `bash 26`, `python 13` and `entries 72`, re-read after the commit rather than carried) 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 **phantom duplicates** by taking the last bracket in a heading that cites other versions. That count is **not a constant — it is a dated measurement** (the same rule this file already imposes on every `- Live:` suite figure): **five** values at `[0.4.65]` (0.4.31/0.4.40/0.4.44/0.4.49/0.4.62), and re-measured **2026-10-03 at `[0.4.174]`** the same pipeline prints **14 lines** — **13 version values** (the five above plus 0.4.70/0.4.75/0.4.80/0.4.96/0.4.102/0.4.110/0.4.162/0.4.172) **plus one empty-string line**: six headings (`[0.4.53]`, `[0.4.83]`, `[0.4.86]`, `[0.4.89]`, `[0.4.93]`, `[0.4.168]`) end on a `failures[]`, so their capture is `""`, and `uniq -d` counts that **value** once however many headings produce it — **14 lines, not 14 headings**. The anchored reading on that same tree (`grep '^## \[' CHANGELOG.md | sed -E 's/^## \[([^]]+)\].*/\1/' | sort | uniq -d`) printed **nothing**: **0** real duplicates. Both figures move whenever an entry cites an earlier version — the very entry that wrote this sentence took the count from **14** to **15** by ending its own heading on `[0.4.65]`, which is the trap firing inside the sentence that documents it — so run the two pipelines before quoting either, never this prose. 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. **`[0.4.82]` pre-commit read**: exit 0, `files_total 192`, `linted {bash 34, go 43, json 11, php 35, python 14}`, `changelog_version entries 86, unique 86, duplicates [], citations_seen 2289, citations_in_series 2246, citations_missing [], series [0.1, 0.2, 0.3, 0.4]`, human line `changelog-version: 86 changelog version heading(s), 86 unique, 2289 citation(s) checked, 0 missing` — all six paths this step touches were **already tracked**, so `files_total` is predicted **unchanged at 192** and `entries` becomes **87** once `[0.4.82]` lands (the citation counts move with the new prose and are therefore re-read after the commit, never carried). **Re-read after `34d1bb2`: exit 0, `files_total 192`, `entries 87`, `unique 87`, `duplicates []`, `citations_missing []` — both predictions hit**; the citation counts read `2367 / 2280 / 0` on that pass and move again with each commit's own prose, which is why they are quoted as a measurement and never as a constant.
3042:- Live (measured 2026-10-02): `./tools/regression-run` → **83 suites** discovered — the suite count this file quotes as current, and the only place it is quoted as current. Machine-checked by `tests/test_registry_coverage.sh` section F: that section re-reads this line and compares it with `regression-run --list` (the same `discover()` a run uses), so adding a suite to `tests/` turns it red until this line is refreshed, a second `- Live:` figure anywhere in this file is `figure_duplicate`, and every `<N> suites` figure elsewhere in this file must carry a `YYYY-MM-DD` on its own line. Assertion totals are printed by the tool itself and are quoted here only as dated history, never as the live claim. *(Refreshed twice on 2026-09-30: **63 → 68** by the run that added `tests/test_start_page.php`, then **68 → 70** when `tests/test_onboarding_baseline.php` and, minutes later, a concurrent shift's `tests/test_heading_order.php` landed — the second number counts the tree `--list` was actually run on (70 files in `tests/`), not a wish list, and the refresh is what clears `VIOLATION figure_stale 63`, which had made section F's controls read 2 violations where they assert exactly one, i.e. 26 reds nobody's code caused; 66 of the 67 are pre-existing suites counted for the first time here. A third refresh, **70 → 73**, landed on 2026-09-30T17:13Z. The starting state was `tests/test_trust_form.php` arriving in `7cd78e0` ("run 410") while this line still said 70, so `--list` answered 71 against a claim of 70 — `VIOLATION figure_stale 70`, read by section F as **28 reds** (the 26 exactly-one-violation controls seeing their plant *plus* the standing one, plus F3 `want=71 got=70`, F5 and F6), every one of them caused by a suite count and none by the code those assertions test. The number was then **in motion while it was being written**: it read **71** at 16:59Z, **72** at 17:05Z (the concurrent run's untracked `tests/test_hire_agent.sh`), and **73** at 17:12Z (`tests/test_commit_gate.sh`, the same run building queue item **(78)**'s pre-commit gate) — three suites landing in thirteen minutes with this line refreshed by none of them, which is why the committed figure is the count read at 17:12Z and why **the run that adds the next suite still owns the next refresh**: this line is a single counted claim, not a wish list, and a number refreshed by a bystander is stale by the time it is pushed. Rewritten by the run that could not re-verify its own step while section F was red for that reason, not by the run that added any of the three — the same attribution question `[0.4.140]`'s heading answered.)* *(Refreshed **73 → 75** on 2026-10-01: **74** is this run's own addition, `tests/test_qa_handover.php` — the guard for `team/QA-HANDOVER.md`, the QA/docs handover `team/ONBOARDING.md`'s first-week item promises — and the **75th** was `tests/test_chat_nojs.php`, present but untracked in the worktree from a concurrent run at the minute this line was re-read. The figure follows what `--list` discovers rather than what any revision happens to hold (the same reading order (83) documented), and the line was measured at commit time instead of carrying yesterday's number over: the run that adds a suite owns the refresh.)* *(Refreshed **75 → 76** on 2026-10-01 (19:05 CEST) as a bystander, and for the second time: `tests/test_chat_header_320.php` (a concurrent shift's `/investor` chat-header fix, `3e50254`) landed as the 76th suite without moving this line, the first refresh was written into the worktree and then lost when this file was rewritten from an older base at 18:57 CEST, so `VIOLATION figure_stale 75` stood through both — 29 reds in `tests/test_registry_coverage.sh`, every one of them caused by a suite count and none by the code those assertions test. Re-measured against `regression-run --list` (76) at the moment this was written, which is what section F compares it with.) *(Refreshed **76 → 77** on 2026-10-02 by the run that added the suite: `tests/test_system_status_staged.sh`, the guard for queue item **(109)(e)** — the `git-tree` row must not call a PARKED STEP healthy. Measured at the moment of the write: `regression-run --list` → **77 suite(s) discovered**, `ls tests | wc -l` → 77, and `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for; the figure follows what `--list` discovers, as the three notes above document.) *(Refreshed **77 → 78** on 2026-10-02 by the run that added `tests/test_template_sync.sh`, the guard for queue item **(113)** — the template copies of the mission documents. Measured at the moment of the write: `regression-run --list` → **78 suite(s) discovered**, `ls tests | wc -l` → 78; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)* *(Refreshed **78 → 79** on 2026-10-02 by the run that added `tests/test_table_scroll_320.php`, the guard for this shift's 320px scroll-box step on `/stats` and `/log` (chrome measurement + 3 mutants). Measured at the moment of the write: `regression-run --list` → **79 suite(s) discovered**, `ls tests | wc -l` → 79; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)* *(Refreshed **79 → 80** on 2026-10-02 by the run that added `tests/test_never_send_from.php`, the guard for the send-from rule: MAIL-POLICY.md §1 states "no identity may ever reply FROM" the send-only address and names two enforcement points, the webmail 403 (pinned by `tests/test_webmail_session_routing.php`) and "shift duties forbid `sendmail -f noreply@gladex.de`", which named no suite — every shift hand-ran the two greps and committed the sentence instead of the check. The new suite reads both live sources with one implementation shared by its controls: every `from=<noreply@gladex.de>` envelope line in `GLADEX_MAIL_LOG`, and the HEADER BLOCK (never the body, so a quotation is not a send) of every delivered message under `GLADEX_MAILDIR_ROOT`, plus the `X-Gladex-Identity: noreply` composer stamp; a planted line in a copy of the real log, a planted sandbox message, a body quotation and an own-address message are the four controls, an absent source is SKIPPED rather than passed, and the suite says so in its result line. Measured at the moment of the write: `regression-run --list` → **80 suite(s) discovered**, `ls tests | wc -l` → 80; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)* *(Refreshed **80 → 81** on 2026-10-02 by the Jonas shift that added `tests/test_start_320.php`, the guard for `/start`'s page-level horizontal scroll: one unbreakable list token (`git://git.gladex.de/gladex.git`) grew the document 6px at a 305px viewport and 21px at 290px, so the whole page scrolled to read a clone URL. Chrome measurement at 290/305/320/1200 on dev + prod, the scoped `overflow-wrap: anywhere` invariant read after comment stripping, `pre.g-code`'s scroller pinned as still load-bearing (10/10 scrolling at 305), and three mutants — declaration dropped, declaration parked in a CSS comment, and `white-space: nowrap` on `.g-list` (which passes the static layer and still scrolls the page, so section 4 cannot be a restatement of section 1). Measured at the moment of the write: `regression-run --list` → **81 suite(s) discovered**, `ls tests | wc -l` → 81; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)* *(Refreshed **81 → 82** on 2026-10-02 by the run that added `tests/test_red_watch.sh`, the hermetic guard for queue item **(116)** (the gap `[0.4.172]` recorded as "no dedicated suite yet"). Measured at the moment of the write: `regression-run --list` → **82 suite(s) discovered**, `ls tests | wc -l` → 82; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)* *(Refreshed **82 → 83** on 2026-10-02 by the run that added `tests/test_system_status_red_watch.sh`, the guard for queue item **(117)** — the `red-watch` row of `tools/system-status` must never report `ok` for a state file it did not read, and must never grow a path that fails the tool (warn-only by construction). Measured at the moment of the write: `regression-run --list` → **83 suite(s) discovered**, `ls tests | wc -l` → 83; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)*
3045:**Status**: Built ✅ 2026-09-26 (193/193; closes `[0.4.65]`'s PROGRESS queue item (1) — the full-regression figure this line used to quote lives only on the dated `- Live:` line above since (63))
3383:of them on a `- Live:` bullet that still said **40** while `regression-run
4328:**Since `[0.4.126]`** — registers with the `- Live:` suite figure **60 → 61**,
4744:moved the `- Live:` figure line above from 63 to **67**.
4957:**Since** this suite landed, `tools/REGISTRY.md`'s `- Live:` suite figure is the
5311:**Status**: Built ✅ 2026-10-02 (queue item **(116)**, `[0.4.173]`; `- Live:`
5396:assertions green in ~15s; `- Live:` suite figure refreshed **82 → 83** in the
$ cd /data/repo && grep -n '(119)' agent-logs/PROGRESS.md | head; echo "=== changelog head"; head -20 CHANGELOG.md
8243:- **Next-candidate queued, not actioned**: **(109)(e) STRUCK by `[0.4.162]`**; **(109)(d) STRUCK by `[0.4.165]`**; **(109)(c) STRUCK by `[0.4.166]`**; **(110) STRUCK by `[0.4.167]`**; **(112) STRUCK by `[0.4.168]`**; **(113) detector LANDED by `[0.4.169]`, copy half BLOCKED ON THE OPERATOR** (the two host `cp`s in `REPORT.md` §14's *OPERATOR HANDOFF*; until they run `tools/template-sync-check` reports `drifted`, the intended verdict); **the commit-message `Z`-mislabel STRUCK by `[0.4.170]`**; **(109)(b) REDESIGNED and LANDED as rule R10 by `[0.4.171]`**; **(114) STRUCK by `[0.4.172]`** (its *other* half — four identities landing through the red with `--no-verify` — is **recorded, not fixed**); **(109)(b)/(c)/(d)'s phantom wording STRUCK by `[0.4.163]`**; **(111) CLOSED by `[0.4.164]`**; **(116) STRUCK by `[0.4.173]`**; **(117) STRUCK by `[0.4.174]`**; **(118) STRUCK by `[0.4.175]` — this run** (two numbers corrected and dated, the empty-string duplicate class recorded for the first time). **New: (119)** — the *rest* of the same class has **no reader**: `tests/test_registry_coverage.sh` section F is the only registry number anyone machine-checks, and it covers `<N> suites` alone. Measured this run: **6** `- Live:` bullets carry a bare number with no `YYYY-MM-DD` (`domain-availability-check`, `dns-verify`, `promote-dev-to-prod`, `ip-drift-check`, `disk-show`, `smtp-relay-probe`), and at least one is provably false as written — `source-sync-check`'s bullet claims **"42 files across dev+prod in sync"** while the tool itself prints **`result: in sync — 44 file(s) across 2 env(s)`** today (`./tools/source-sync-check` → exit 0, measured 2026-10-03), and `disk-show`'s quotes `/` at **77.5 %** against a `df` reading of **74 %** this same week. The follow-up is to decide the shape — date the six bullets (cheap, immediate) or extend section F's rule to any undated numeric `- Live:` claim (guards it, costs a control per bullet) — not to keep discovering them one run at a time. Live head stays **(99)** — the dead-letter agent loop, whose fix only takes effect **with a service restart** this loop does not perform unilaterally — then (113)'s copy whenever the host account runs it, then **(119)**. Carry unchanged and all standing: (2), (4)–(8), (14), (15), (17), (18), (20), (22), (23), (25), (28), (32), (36), (39), (42), (44), (47), (48), (52), (53), (54), (56), (60), (61), (62), (66), (67), (68), (70), (99), plus **(93)**, **(96) candidate (b)** and **(97)** blocked on the founder's answer to reply dev 141 / prod 106, plus **(115)**'s two durable halves (re-measured non-reproducing three times) and **(114)**'s recorded-not-fixed half. Blocked on the investor: **(93)**, **(96)(b)**, **(97)**, `hiring/queue/ruben-stoll.json`, and the **`.de` renewal date** (`REPORT.md` §14 / reply dev 142 / prod 107).
8245:- **Queue left as written**: **(109)(e) STRUCK by `[0.4.162]`**; **(109)(d) STRUCK by `[0.4.165]`**; **(109)(c) STRUCK by `[0.4.166]`**; **(110) STRUCK by `[0.4.167]`**; **(112) STRUCK by `[0.4.168]`**; **(113) detector LANDED by `[0.4.169]`, its copy blocked on the operator**; **the commit-message `Z`-mislabel STRUCK by `[0.4.170]`**; **(109)(b) REDESIGNED and LANDED as R10 by `[0.4.171]`**; **(114) STRUCK by `[0.4.172]`**; **(109)(b)/(c)/(d)'s phantom wording STRUCK by `[0.4.163]`**; **(111) CLOSED by `[0.4.164]`**; **(115)** recorded, premise re-measured three times as not reproducing, durable halves open; **(116) STRUCK by `[0.4.173]`**; **(117) STRUCK by `[0.4.174]`**; **(118) STRUCK by `[0.4.175]`, landed by this run's commit**; **(119)** new. Live head **(99)** (needs a service restart this loop does not perform unilaterally), then (113)'s copy, then **(119)**. **Next run**: **(93)** or **(97)(a)** the moment the investor answers (STEP 0 still 0 unread / 0 open), otherwise **(119)** — with (118) struck it is the only named item whose blocker is work I can do rather than a restart, an operator or the founder. Nothing else moved this run: **no `app/src/php` edit → no reviewer gate and no promote**, prod untouched **0.4.28**; census **24 tools / 36 registered sections / 83 suites**, `- Live:` **83** untouched; **no cron change, nothing restarted**; **zero DNS writes**; **no mail sent**; no history rewrite; no other identity's file edited by me. Model spend **0.00** (free `*-free` models only).
8253:- **CLOSE PROTOCOL — this closing section is appended to `agent-logs/PROGRESS.md` *before* commit #2, and commit #2 carries exactly `git commit -- agent-logs/PROGRESS.md` (no `git add -A`).** After that push, `git status --porcelain` must not list `agent-logs/PROGRESS.md`. Census closes **24 tools / 36 registered sections / 83 suites / `- Live: 83` / `GLADEX_APP_VERSION` 0.4.28 / spend 0.00 € of 5.00 € (month 2026-10)**, with one visible step taken — **(118)** — and **(119)** queued as the next runnable one.
=== changelog head
# Changelog

All notable changes to Gladex are documented here.

Format: `[version] - YYYY-MM-DD — description`

**Which version heading is "newest"? Three readings, three answers — never infer one from another.** The *first* heading below is the app version train: it is what `app/src/go/build.sh` parses, what `const GLADEX_APP_VERSION` must equal, and what `/api/changelog` returns first — that endpoint reads the file in order and does not sort — so it moves only when an entry is written at the top of this file. The *last* heading is the most recently written entry, because everything since the legacy block is appended to the bottom. The *highest* version is what a human reader wants and what the `/changelog` page's "Latest 5 releases" digest leads with, because that digest sorts with `version_compare`. This file is legacy-descending at the top and append-ascending at the bottom, so those three readings are three different numbers today — pick the one your reader needs on purpose.

## [0.4.28] - 2026-09-24 — Go CLI joins the single version train: build.sh derives its version from this changelog (was a hardcoded 0.2.0), download badge + docs CLI mentions follow, all four binaries rebuilt

### Fixed
- **Go CLI version train** (queued at 0.4.27): `app/src/go/build.sh` hardcoded `VERSION="0.2.0"`, so every rebuild reported **0.2.0** while the product train had moved to 0.4.x — the downloaded binary, the `/download` badge (`$version = '0.2.0'`) and two `/docs` mentions (`Go CLI binary (v0.2.0)` and the ASCII diagram's `gladex v0.2.0`) advertised numbers ~26 entries behind this changelog: exactly the drift class 0.4.27 closed for the app's nine stale `0.3.0` literals, just on the CLI side. `build.sh` also ignored the README-documented `VERSION=x.y.z ./build.sh` override (silently clobbered by the literal).
- **`gladex version --remote` was structurally incoherent**: it compares the local binary against `https://gladex.de/api/version` (the **app** train), so a CLI pinned at 0.2.0 reported "update available" against every already-current download — and would keep reporting it at whatever stale number the train stopped at. After this fix the check compares like with like: changelog top = app const = binary `version --json` = download badge = docs mentions.
- Fix: `build.sh` parses the top `## [x.y.z]` entry out of `CHANGELOG.md` (`VERSION=` env override preserved — README's documented custom-version invocation now actually works instead of being ignored); `/download`'s badge reads `GLADEX_APP_VERSION` instead of a literal; the two `/docs` CLI mentions carry the current train number and are now **guarded by a test** (they were unguarded, i.e. permanently stale-prone). App train moved to **0.4.28** with it (`GLADEX_APP_VERSION` + `/docs` payloads/footer, repo+dev+prod byte-identical). `build.sh` also `cd`s into its own directory first — module resolution starts at the CWD, so invoking it from anywhere but `app/src/go` (the repo root, `/tmp`) died with `go: cannot find main module` even though every other path in the script was already `SCRIPT_DIR`-relative; guarded behaviourally by building from the repo root.
- Rebuilt all four copies — `app/bin/gladex`, `app/src/go/gladex`, dev, prod — md5-identical, reporting `0.4.28`.

### Tests
- **`tests/test_cli_version.php`** (35 assertions, 3 mutations): `build.sh` derives from the changelog with no hardcoded `VERSION="x.y.z"` literal, keeps the env override and `cd`s to its script dir (behavioural: built from the repo root — the cwd trap — reporting the changelog top, and from `/tmp` with `VERSION=9.9.9` where the override must win); `download.php` has no version literal (token scan; comments don't count) and renders the badge from the const (behavioural: const `9.9.9` → badge `v9.9.9`, proving wiring not coincidence; standalone-without-const → `0.0.0` sentinel); the two `/docs` CLI mentions parse and equal the changelog top (stale number → FAIL); all four binaries report the changelog top via `version --json` with a real commit sha and are md5-identical. Mutations that MUST be detected: reintroduced `$version = '0.2.0'` (static + behavioural), reintroduced `VERSION="0.2.0"` in build.sh, stale `gladex v0.1.0` docs mention.

### Notes
> build · mimo-v2.6-flash-free
$ cd /data/repo && awk '/^- Live:/ || /^- Live / {n++; hasdate = ($0 ~ /[0-9]{4}-[0-9]{2}-[0-9]{2}/); hasnum = ($0 ~ /[0-9]/); printf "%s | date=%s | num=%s | %s\n", (hasnum?"NUM":"--"), (hasdate?"Y":"N"), (hasnum?"Y":"N"), substr($0,1,110)} END{print "total bullets:", n}' tools/REGISTRY.md
NUM | date=N | num=Y | - Live: `domain-availability-check gladex.de` → exit 3 (taken); a free `.de` → exit 0; `--format human gladex.
NUM | date=Y | num=Y | - Live: `budget-show` → 0; `--format json` → balanced 5.00 − 0.00 = 5.00 (`month: 2026-10`, rolled 2026-10-01;
NUM | date=N | num=Y | - Live: `dns-verify gladex.de` → exit 0, `gladex.de BOGUSTYPE` → exit 2 (was 0 + fabricated record), `--server
NUM | date=Y | num=Y | - Live (2026-09-25, after this step): `promote-dev-to-prod --dry-run --force --format json` →
NUM | date=N | num=Y | - Live: `promote-dev-to-prod --dry-run` → exit 5 while `reviewer-to-main/` is empty
NUM | date=Y | num=Y | - Live (2026-09-25, `[0.4.47]`): `promote-dev-to-prod --dry-run --force --format json`
NUM | date=N | num=Y | - Live: `ip-drift-check --format json --domain gladex.de` → exit 0, `drift:false` (tunnel up); `--help` → 0.
NUM | date=N | num=Y | - Live: `smtp-relay-probe` → exit 0; `:587` `:465` deny `554 5.7.1`, public path denies `454 4.7.1`, all four 
NUM | date=Y | num=Y | - Live: `source-sync-check` → `exit 0`, 42 files across dev+prod in sync (after the 2026-09-24 drift fix); sam
NUM | date=Y | num=Y | - Live: HEAD → exit 0 (the live file count is `files_total` in `repo-lint --format json` — **270** measured 20
NUM | date=Y | num=Y | - Live (measured 2026-10-02): `./tools/regression-run` → **83 suites** discovered — the suite count this file 
total bullets: 11
$ cd /data/repo && ./tools/source-sync-check; echo "exit=$?"; echo "=== disk-show"; ./tools/disk-show 2>&1 | head -30; echo "=== df"; df -h / | tail -2
source-sync-check  repo=/data/repo  env=both  2026-10-03T01:21:22Z
  dev  examples/workflows       8 files  OK
  dev  src/php                 14 files  OK
  prod examples/workflows       8 files  OK
  prod src/php                 14 files  OK
result: in sync — 44 file(s) across 2 env(s) (exit 0)
exit=0
=== disk-show
=== Disk Status ===
Threshold: 90%

Watched paths:
  /                 73.5%  used 73320062976 B / avail 26383712256 B
  /data              6.0%  used 6936096768 B / avail 109394403328 B

Reclaimable (report only - disk-show never deletes):
  go-build-cache           13669685467 B  /tmp/gocache
  go-modcache-gomod        545549311 B  /tmp/gomod
  go-modcache-gomodcache   833050791 B  /tmp/gomodcache
  go-modcache-gocache      absent  /tmp/go-cache
  go-modcache-gomod2       absent  /tmp/go-modcache
  go-modcache-go           absent  /tmp/go
  go-modcache-gm2          absent  /tmp/gm2
  gopath-tmp               703141043 B  /tmp/gopath
  gopath-root              1324031577 B  /root/go
  go-build-root            absent  /root/.cache/go-build
  pip-cache                absent  /root/.cache/pip
  apt-archives             36684 B  /var/cache/apt/archives
  loop-scratch             unmeasured (timeout)  /tmp/opencode
  reclaimable total: 17075494873 B across 13 paths (1 unmeasured)

OK: all 2 watched path(s) below 90% (worst / at 73.5%)
=== df
Filesystem                         Size  Used Avail Use% Mounted on
/dev/mapper/ubuntu--vg-ubuntu--lv   98G   69G   25G  74% /
> build · mimo-v2.6-flash-free
$ cd /data/repo && grep -n 'disk-show' tools/REGISTRY.md | head -20; echo "=== source-sync section"; grep -n 'source-sync-check' tools/REGISTRY.md | head
1090:## disk-show
1097:four duplicate module caches). `disk-show` measures the watched paths, prints the
1103:**Location**: `/data/repo/tools/disk-show`
1105:**Usage**: `disk-show [--format json|human] [--threshold PCT] [--mount PATH]`
1157:- Live, content-agnostic (no amount pinned, so a future purchase or cleanup cannot spuriously redden it): a real `disk-show --format json` exits **0** with `/` at **77.5 %** and `/data` at **5.1 %**, watches `/` and `/data`, every reclaimable row carrying name/path/note
3028:**Free-space preflight (the (82) half `disk-show` cannot be, since `[0.4.129]`)**: `disk-show` watches mounts on a **percentage** and lists reclaimable caches; this gates a **run** on an **absolute MB floor**, because the failure it guards against is the one measured 2026-09-29 — the root filesystem reached **100 % (98 G, 0 available)** and the damage was *silent* (writes failing, sqlite `disk I/O error`, shell output discarded at exit 0), and this tool is the one whose own `--log-dir` writes and whose suites' `mktemp` sandboxes are a large part of what fills `/tmp`. A disk at 79 % with 21 G free is healthy; one at 90 % with 0 bytes is not, so the number that matters is bytes left, not percent used. Watched paths, in order: repo root, tests dir, `TMPDIR` (default `/tmp`), and — with `--log-dir` — the nearest **existing** ancestor of it (the directory is created *after* the check, so `statvfs` on the path itself could not run, and a check that cannot measure must refuse rather than pass). Measured with `statvfs` (`f_bavail × f_frsize`, MB truncated); duplicates collapse to one measurement. The refusal names the **first** short path with both numbers. `--list` is exempt — it runs nothing and writes nothing, and the gate exists to protect work. Placement is the contract: the call sits **after** the `--list` branch and **before** `os.makedirs(args.log_dir)`, pinned by `test_regression_run.sh` K28, so "refused before the first suite" is a line-order fact rather than a claim. Costs one `statvfs` per path on the healthy path (K1), and `GLADEX_MIN_FREE_MB=0` is the documented off switch.
=== source-sync section
297:promotion. Step 0b runs `tools/source-sync-check --env dev --format json` (read-only,
300:- the comparison cannot be performed at all (`source-sync-check` missing, times out, or
304:`tools/source-sync-check --env dev` for the full list; the fix is always *commit the dev
306:Delegating to `source-sync-check` rather than reimplementing the comparison is deliberate:
428:            "child": "/data/repo/tools/source-sync-check"},
448:(`commit-lint` → `repo-lint`, `dev-sync` → `source-sync-check`; the verdict gate
457:`system-status` row's *own* `source-sync-check --env dev` run — the second
505:Kept in step with `source-sync-check`, which compares exactly these two trees; a
533:**Dependencies**: git, rsync, composer (optional), systemctl, healthcheck tool, source-sync-check, repo-lint, python3
542:  rsync-planned, that this tool and `source-sync-check` cover the same tree set, gate
$ cd /data/repo && grep -n 'Live' tools/REGISTRY.md | grep -v '^- Live' | head -30; echo "=== count all Live lines"; grep -c 'Live' tools/REGISTRY.md
66:- Live: `domain-availability-check gladex.de` → exit 3 (taken); a free `.de` → exit 0; `--format human gladex.de` → `TAKEN` with all three sources listed.
132:- Live: `budget-show` → 0; `--format json` → balanced 5.00 − 0.00 = 5.00 (`month: 2026-10`, rolled 2026-10-01; September closed 1.50 / 3.50, history kept verbatim — the `spent:` note carries the open `.de` renewal question, REPORT.md §14) with `"warnings": []` while the month is current, and `GLADEX_CURRENT_MONTH=2099-12` → still exit **0** with exactly one warning; `/api/budget` on :8000/:8001 → same numbers, both pages `200` (PHP parses the ledger independently)
198:- 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
673:  Live transcript (real `repo-lint` child, real tool, throwaway repo): the changelog
750:- Live (2026-09-25, after this step): `promote-dev-to-prod --dry-run --force --format json` →
757:- Live: `promote-dev-to-prod --dry-run` → exit 5 while `reviewer-to-main/` is empty
762:- Live (2026-09-25, `[0.4.47]`): `promote-dev-to-prod --dry-run --force --format json`
850:- Live: `ip-drift-check --format json --domain gladex.de` → exit 0, `drift:false` (tunnel up); `--help` → 0.
907:- **Live (real zone, 2026-09-24)**: `zones` → `200 ["gladex.de."]`; `records` → the real rrset listing; TXT probe `set-txt gladex.de _agentprobe …` → `204 OK`, stored as `"…"`, **`dig +short TXT _agentprobe.gladex.de` resolved it**, then `delete … TXT` → `204 OK` and gone. This run is also what exposed that the live API answers `/zones` with a **bare array**, not the documented `{"zones": [...]}` object — the shape the fake server had wrongly modelled, now covered by `zones`/`wrapped`/`scalar` scenarios. Live gate proofs: `delete … NS`, `set-a other.de …`, `set-a … 86400` all exit 2 without contacting the API.
1157:- Live, content-agnostic (no amount pinned, so a future purchase or cleanup cannot spuriously redden it): a real `disk-show --format json` exits **0** with `/` at **77.5 %** and `/data` at **5.1 %**, watches `/` and `/data`, every reclaimable row carrying name/path/note
1160:**Status**: Built ✅ 2026-09-29 (queue item **(82)**; the root-filesystem watchdog). Registers with the `- Live:` suite figure **61 → 62**, which is what turns `tests/test_registry_coverage.sh` section F red until that line is refreshed in the same commit that adds the suite.
1738:  Live: `repo-lint --format json --sha HEAD` → `go_compile.ok true`; the dashboard
2036:  Live (`[0.4.46]` run): `promote-gates [WARN] not promotable (reviewer-mailbox
2052:   Live (`[0.4.47]` run): `promote-gates [WARN] not promotable (reviewer-mailbox
2059:   Live (`[0.4.49]` run): `promote-gates [WARN] not promotable
2398:**Purpose**: Verify SMTP relay stays CLOSED on our mail ports — unauthenticated external RCPT must be refused — AND that every endpoint still greets correctly. Live-probes :25/:587/:465 locally and through the full public path (VPS 77.90.15.49:25 forward). Never sends DATA, so no message can ever be queued. The **220-banner assertion** (exit 4) guards against a regression of the 2026-09-23 :25-greeting bug: a connected endpoint that does not greet `220 ` within the timeout is a hard failure, never an "ok" — one broken endpoint among four can no longer pass green.
2441:- 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
2492:- 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)
2956:- **Section N — the compile gate** (its own stdlib-only module in a separate throwaway repo; `$SB/repo`'s `main.go`-without-`go.mod` is case N10): clean module → 0 with `go_compile.ok`; **the 395b9b5 shape verbatim → 1** naming `pkg/a_test.go:4 [go-compile]` *and* asserting `all(f['lang'] != 'go')` + `linted == {'go': 2}` — the per-file gate passed, proving the parse gate is blind rather than merely not reached; blob basis (worktree fix invisible → 1; committed fix → 0); broken *ancestor* `--sha` → 1 while HEAD is clean; `GLADEX_GO_GOCACHE=/dev/null/x` → **3 with `failures == []`** (cannot verify ≠ broken ≠ pass); missing `go` → 3 naming it; `--go-timeout 0.001` → 3 and `--go-timeout 0` → 2; `--help` documents the gate. Live, **content-addressed so it can never flake**: `--sha 395b9b5` must exit 1 blaming `app/src/go/cmd/gladex/commands/status_test.go:5` as `go-compile` and nothing else; `--sha 219fd8f` still exactly one failure (the PHP parse error) **with a passing Go gate**; HEAD `go_compile.go_mod == app/src/go/go.mod`, `files > 40`.
2975:- Live: HEAD → exit 0 (the live file count is `files_total` in `repo-lint --format json` — **270** measured 2026-10-03; this bullet's own opening figure was **176 files**, the `[0.4.65]`-era (2026-09-26) count, whose dated history follows — **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. **`[0.4.66]` pre-commit read: exit 0, `files_total 176`, `linted {bash 25, go 43, json 11, php 32, python 12}`, `changelog_version entries 70, unique 70, duplicates []`** — both changed files were already tracked, so the post-commit prediction is **176 unchanged** with `entries 71` once `[0.4.66]` lands, re-read after the commit rather than carried. **`[0.4.67]` pre-commit read: exit 0, `files_total 176`, `linted {bash 25, go 43, json 11, php 32, python 12}`, `changelog_version entries 71, unique 71, duplicates []`** — this entry adds two **untracked** files (`tools/queue-source-check`, extensionless `python3` shebang → `linted.python`; `tests/test_queue_source.sh`, `.sh` → `linted.bash`) plus its own heading, so the post-commit prediction is **178** with `bash 26`, `python 13` and `entries 72`, re-read after the commit rather than carried) 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 **phantom duplicates** by taking the last bracket in a heading that cites other versions. That count is **not a constant — it is a dated measurement** (the same rule this file already imposes on every `- Live:` suite figure): **five** values at `[0.4.65]` (0.4.31/0.4.40/0.4.44/0.4.49/0.4.62), and re-measured **2026-10-03 at `[0.4.174]`** the same pipeline prints **14 lines** — **13 version values** (the five above plus 0.4.70/0.4.75/0.4.80/0.4.96/0.4.102/0.4.110/0.4.162/0.4.172) **plus one empty-string line**: six headings (`[0.4.53]`, `[0.4.83]`, `[0.4.86]`, `[0.4.89]`, `[0.4.93]`, `[0.4.168]`) end on a `failures[]`, so their capture is `""`, and `uniq -d` counts that **value** once however many headings produce it — **14 lines, not 14 headings**. The anchored reading on that same tree (`grep '^## \[' CHANGELOG.md | sed -E 's/^## \[([^]]+)\].*/\1/' | sort | uniq -d`) printed **nothing**: **0** real duplicates. Both figures move whenever an entry cites an earlier version — the very entry that wrote this sentence took the count from **14** to **15** by ending its own heading on `[0.4.65]`, which is the trap firing inside the sentence that documents it — so run the two pipelines before quoting either, never this prose. 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. **`[0.4.82]` pre-commit read**: exit 0, `files_total 192`, `linted {bash 34, go 43, json 11, php 35, python 14}`, `changelog_version entries 86, unique 86, duplicates [], citations_seen 2289, citations_in_series 2246, citations_missing [], series [0.1, 0.2, 0.3, 0.4]`, human line `changelog-version: 86 changelog version heading(s), 86 unique, 2289 citation(s) checked, 0 missing` — all six paths this step touches were **already tracked**, so `files_total` is predicted **unchanged at 192** and `entries` becomes **87** once `[0.4.82]` lands (the citation counts move with the new prose and are therefore re-read after the commit, never carried). **Re-read after `34d1bb2`: exit 0, `files_total 192`, `entries 87`, `unique 87`, `duplicates []`, `citations_missing []` — both predictions hit**; the citation counts read `2367 / 2280 / 0` on that pass and move again with each commit's own prose, which is why they are quoted as a measurement and never as a constant.
2978:**Status**: Green ✅ on 2026-09-27 (**348/348**; full regression that day: **52 suites / 4267 assertions / 0 failed / 0 skipped, exit 0** — 29 shell = 2892, 23 PHP = 1375. Closure is arithmetic: the previous green run was **52 / 4243** with this suite at **324**, and this step adds **+24 (324 → 348) touching no other suite**, so 4243 + 24 = 4267 with 52 = 52 suites, and the shell split moves 2868 → 2892 by the same 24 while PHP stays 1375. The regression clause that follows was **carried unchanged at 41 / 3503 since `[0.4.67]`** — the tree had already reached 52 / 4210 by `[0.4.80]` without this line being touched, the same stale-line class the entry above records — so it is **refreshed here rather than silently extended**. History: 41 suites / 3503 / 0 failed / 21 shell = 2312, 20 PHP = 1191 at 291/291 on 2026-09-26. Was 40 / 3396 / 20 shell = 2205 with `[0.4.66]`; **`[0.4.67]` adds `tests/test_queue_source.sh` (+1 suite, +107 shell), so 2205 + 107 = 2312 and 3396 + 107 = 3503**, PHP untouched at 1191. Before that, `[0.4.66]` grew `tests/test_regression_run.sh` (+0 suites, +83 shell: 110 → 193), so 2122 + 83 = 2205 and 3313 + 83 = 3396, and `[0.4.65]` added that suite (+1 suite, +110 shell) — 2012 + 110 = 2122, 3203 + 110 = 3313. This line was itself two runs stale at `[0.4.64]` — `[0.4.63]` added `test_app_contrast.php` (+1, +185) without updating it, so 958 + 185 + 48 = 1191 and 2970 + 185 + 48 = 3203 came out then). Since queue item **(63)** (2026-09-28) this Status line no longer claims to be *the current* figure: it is dated history, the live total lives **once** on `## regression-run`'s `- Live (measured YYYY-MM-DD)` line, and `tests/test_registry_coverage.sh` section F reads that line and compares it with `regression-run --list` — so a second copy of the number here would be a second truth nobody compared, which is the (55)/(57)/(59) shape one child further along)
3042:- Live (measured 2026-10-02): `./tools/regression-run` → **83 suites** discovered — the suite count this file quotes as current, and the only place it is quoted as current. Machine-checked by `tests/test_registry_coverage.sh` section F: that section re-reads this line and compares it with `regression-run --list` (the same `discover()` a run uses), so adding a suite to `tests/` turns it red until this line is refreshed, a second `- Live:` figure anywhere in this file is `figure_duplicate`, and every `<N> suites` figure elsewhere in this file must carry a `YYYY-MM-DD` on its own line. Assertion totals are printed by the tool itself and are quoted here only as dated history, never as the live claim. *(Refreshed twice on 2026-09-30: **63 → 68** by the run that added `tests/test_start_page.php`, then **68 → 70** when `tests/test_onboarding_baseline.php` and, minutes later, a concurrent shift's `tests/test_heading_order.php` landed — the second number counts the tree `--list` was actually run on (70 files in `tests/`), not a wish list, and the refresh is what clears `VIOLATION figure_stale 63`, which had made section F's controls read 2 violations where they assert exactly one, i.e. 26 reds nobody's code caused; 66 of the 67 are pre-existing suites counted for the first time here. A third refresh, **70 → 73**, landed on 2026-09-30T17:13Z. The starting state was `tests/test_trust_form.php` arriving in `7cd78e0` ("run 410") while this line still said 70, so `--list` answered 71 against a claim of 70 — `VIOLATION figure_stale 70`, read by section F as **28 reds** (the 26 exactly-one-violation controls seeing their plant *plus* the standing one, plus F3 `want=71 got=70`, F5 and F6), every one of them caused by a suite count and none by the code those assertions test. The number was then **in motion while it was being written**: it read **71** at 16:59Z, **72** at 17:05Z (the concurrent run's untracked `tests/test_hire_agent.sh`), and **73** at 17:12Z (`tests/test_commit_gate.sh`, the same run building queue item **(78)**'s pre-commit gate) — three suites landing in thirteen minutes with this line refreshed by none of them, which is why the committed figure is the count read at 17:12Z and why **the run that adds the next suite still owns the next refresh**: this line is a single counted claim, not a wish list, and a number refreshed by a bystander is stale by the time it is pushed. Rewritten by the run that could not re-verify its own step while section F was red for that reason, not by the run that added any of the three — the same attribution question `[0.4.140]`'s heading answered.)* *(Refreshed **73 → 75** on 2026-10-01: **74** is this run's own addition, `tests/test_qa_handover.php` — the guard for `team/QA-HANDOVER.md`, the QA/docs handover `team/ONBOARDING.md`'s first-week item promises — and the **75th** was `tests/test_chat_nojs.php`, present but untracked in the worktree from a concurrent run at the minute this line was re-read. The figure follows what `--list` discovers rather than what any revision happens to hold (the same reading order (83) documented), and the line was measured at commit time instead of carrying yesterday's number over: the run that adds a suite owns the refresh.)* *(Refreshed **75 → 76** on 2026-10-01 (19:05 CEST) as a bystander, and for the second time: `tests/test_chat_header_320.php` (a concurrent shift's `/investor` chat-header fix, `3e50254`) landed as the 76th suite without moving this line, the first refresh was written into the worktree and then lost when this file was rewritten from an older base at 18:57 CEST, so `VIOLATION figure_stale 75` stood through both — 29 reds in `tests/test_registry_coverage.sh`, every one of them caused by a suite count and none by the code those assertions test. Re-measured against `regression-run --list` (76) at the moment this was written, which is what section F compares it with.) *(Refreshed **76 → 77** on 2026-10-02 by the run that added the suite: `tests/test_system_status_staged.sh`, the guard for queue item **(109)(e)** — the `git-tree` row must not call a PARKED STEP healthy. Measured at the moment of the write: `regression-run --list` → **77 suite(s) discovered**, `ls tests | wc -l` → 77, and `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for; the figure follows what `--list` discovers, as the three notes above document.) *(Refreshed **77 → 78** on 2026-10-02 by the run that added `tests/test_template_sync.sh`, the guard for queue item **(113)** — the template copies of the mission documents. Measured at the moment of the write: `regression-run --list` → **78 suite(s) discovered**, `ls tests | wc -l` → 78; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)* *(Refreshed **78 → 79** on 2026-10-02 by the run that added `tests/test_table_scroll_320.php`, the guard for this shift's 320px scroll-box step on `/stats` and `/log` (chrome measurement + 3 mutants). Measured at the moment of the write: `regression-run --list` → **79 suite(s) discovered**, `ls tests | wc -l` → 79; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)* *(Refreshed **79 → 80** on 2026-10-02 by the run that added `tests/test_never_send_from.php`, the guard for the send-from rule: MAIL-POLICY.md §1 states "no identity may ever reply FROM" the send-only address and names two enforcement points, the webmail 403 (pinned by `tests/test_webmail_session_routing.php`) and "shift duties forbid `sendmail -f noreply@gladex.de`", which named no suite — every shift hand-ran the two greps and committed the sentence instead of the check. The new suite reads both live sources with one implementation shared by its controls: every `from=<noreply@gladex.de>` envelope line in `GLADEX_MAIL_LOG`, and the HEADER BLOCK (never the body, so a quotation is not a send) of every delivered message under `GLADEX_MAILDIR_ROOT`, plus the `X-Gladex-Identity: noreply` composer stamp; a planted line in a copy of the real log, a planted sandbox message, a body quotation and an own-address message are the four controls, an absent source is SKIPPED rather than passed, and the suite says so in its result line. Measured at the moment of the write: `regression-run --list` → **80 suite(s) discovered**, `ls tests | wc -l` → 80; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)* *(Refreshed **80 → 81** on 2026-10-02 by the Jonas shift that added `tests/test_start_320.php`, the guard for `/start`'s page-level horizontal scroll: one unbreakable list token (`git://git.gladex.de/gladex.git`) grew the document 6px at a 305px viewport and 21px at 290px, so the whole page scrolled to read a clone URL. Chrome measurement at 290/305/320/1200 on dev + prod, the scoped `overflow-wrap: anywhere` invariant read after comment stripping, `pre.g-code`'s scroller pinned as still load-bearing (10/10 scrolling at 305), and three mutants — declaration dropped, declaration parked in a CSS comment, and `white-space: nowrap` on `.g-list` (which passes the static layer and still scrolls the page, so section 4 cannot be a restatement of section 1). Measured at the moment of the write: `regression-run --list` → **81 suite(s) discovered**, `ls tests | wc -l` → 81; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)* *(Refreshed **81 → 82** on 2026-10-02 by the run that added `tests/test_red_watch.sh`, the hermetic guard for queue item **(116)** (the gap `[0.4.172]` recorded as "no dedicated suite yet"). Measured at the moment of the write: `regression-run --list` → **82 suite(s) discovered**, `ls tests | wc -l` → 82; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)* *(Refreshed **82 → 83** on 2026-10-02 by the run that added `tests/test_system_status_red_watch.sh`, the guard for queue item **(117)** — the `red-watch` row of `tools/system-status` must never report `ok` for a state file it did not read, and must never grow a path that fails the tool (warn-only by construction). Measured at the moment of the write: `regression-run --list` → **83 suite(s) discovered**, `ls tests | wc -l` → 83; `tests/test_registry_coverage.sh` section F is the reader this line is refreshed for, and the figure follows what `--list` discovers rather than what the run hopes for.)*
3045:**Status**: Built ✅ 2026-09-26 (193/193; closes `[0.4.65]`'s PROGRESS queue item (1) — the full-regression figure this line used to quote lives only on the dated `- Live:` line above since (63))
3383:of them on a `- Live:` bullet that still said **40** while `regression-run
3385:requires exactly one such Live line, checks its count against the tool's own
3664:  kind (**C16** stale count, **C17** undated figure, **C18** no Live line,
3665:  **C19** a second Live line, **C20** the number stripped from an intact line),
3730:  **F** the regression figure: exactly one `- Live` line carries a suite count,
3749:  against a copy of this file: a Live line reading **40** where `--list` says
3752:  from — the one-code-path property showing itself); a Live line with its date
=== count all Live lines
37
> build · mimo-v2.6-flash-free

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 Oct  3 03:08:02 2026] 127.0.0.1:57172 Accepted
[Sat Oct  3 03:08:02 2026] 127.0.0.1:57172 Closing
[Sat Oct  3 03:08:02 2026] 127.0.0.1:57188 Accepted
[Sat Oct  3 03:08:02 2026] 127.0.0.1:57188 Closing
[Sat Oct  3 03:08:02 2026] 127.0.0.1:57196 Accepted
[Sat Oct  3 03:08:02 2026] 127.0.0.1:57196 Closing
[Sat Oct  3 03:08:02 2026] 127.0.0.1:57204 Accepted
[Sat Oct  3 03:08:02 2026] 127.0.0.1:57204 Closing
[Sat Oct  3 03:08:02 2026] 127.0.0.1:57214 Accepted
[Sat Oct  3 03:08:02 2026] 127.0.0.1:57214 Closing
[Sat Oct  3 03:08:02 2026] 127.0.0.1:57222 Accepted
[Sat Oct  3 03:08:03 2026] 127.0.0.1:57222 Closing
[Sat Oct  3 03:08:03 2026] 127.0.0.1:57226 Accepted
[Sat Oct  3 03:08:03 2026] 127.0.0.1:57226 Closing
[Sat Oct  3 03:10:05 2026] 127.0.0.1:48126 Accepted
[Sat Oct  3 03:10:05 2026] 127.0.0.1:48126 Closing
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42828 Accepted
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42828 Closing
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42842 Accepted
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42842 Closing
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42844 Accepted
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42844 Closing
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42854 Accepted
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42854 Closing
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42858 Accepted
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42858 Closing
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42860 Accepted
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42860 Closing
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42874 Accepted
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42874 Closing
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42890 Accepted
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42890 Closing
[Sat Oct  3 03:15:08 2026] 127.0.0.1:42894 Accepted
[Sat Oct  3 03:15:09 2026] 127.0.0.1:42894 Closing
[Sat Oct  3 03:15:09 2026] 127.0.0.1:42904 Accepted
[Sat Oct  3 03:15:09 2026] 127.0.0.1:42904 Closing
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36352 Accepted
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36352 Closing
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36360 Accepted
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36360 Closing
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36362 Accepted
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36362 Closing
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36366 Accepted
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36366 Closing
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36376 Accepted
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36376 Closing
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36388 Accepted
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36388 Closing
[Sat Oct  3 03:15:53 2026] 127.0.0.1:36394 Accepted
[Sat Oct  3 03:15:54 2026] 127.0.0.1:36394 Closing
[Sat Oct  3 03:15:54 2026] 127.0.0.1:36406 Accepted
[Sat Oct  3 03:15:54 2026] 127.0.0.1:36406 Closing
[Sat Oct  3 03:15:54 2026] 127.0.0.1:36420 Accepted
[Sat Oct  3 03:15:54 2026] 127.0.0.1:36420 Closing
[Sat Oct  3 03:15:54 2026] 127.0.0.1:36432 Accepted
[Sat Oct  3 03:15:54 2026] 127.0.0.1:36432 Closing
[Sat Oct  3 03:18:40 2026] 127.0.0.1:43786 Accepted
[Sat Oct  3 03:18:40 2026] 127.0.0.1:43786 Closing
[Sat Oct  3 03:22:08 2026] 127.0.0.1:56108 Accepted

Generated 2026-10-03 01:22:08 UTC · Gladex.de