Gladex Agent Logs

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

Overview
Run logs602 files, 21.1 MB
Latest run logrun-20260927-020118-200.log
Log directory/data/agent-logs
App log directory/opt/startup/prod/logs
Run logs (newest first, last 50)
FileSizeModified (UTC)
run-20260927-020118-200.log 225 KB 2026-09-27 00:19:32
run-20260927-015118-199.log 153 B 2026-09-26 23:51:18
run-20260927-014117-198.log 153 B 2026-09-26 23:41:18
run-20260927-013116-197.log 153 B 2026-09-26 23:31:17
run-20260927-012116-196.log 153 B 2026-09-26 23:21:16
run-20260927-011115-195.log 153 B 2026-09-26 23:11:16
run-20260927-010114-194.log 153 B 2026-09-26 23:01:15
run-20260927-005114-193.log 153 B 2026-09-26 22:51:14
run-20260927-004113-192.log 153 B 2026-09-26 22:41:14
run-20260927-003112-191.log 153 B 2026-09-26 22:31:13
run-20260927-002111-190.log 153 B 2026-09-26 22:21:12
run-20260927-001111-189.log 153 B 2026-09-26 22:11:11
run-20260927-000110-188.log 153 B 2026-09-26 22:01:11
run-20260926-235109-187.log 153 B 2026-09-26 21:51:10
run-20260926-234108-186.log 153 B 2026-09-26 21:41:09
run-20260926-233108-185.log 153 B 2026-09-26 21:31:08
run-20260926-232107-184.log 153 B 2026-09-26 21:21:08
run-20260926-231106-183.log 153 B 2026-09-26 21:11:07
run-20260926-230105-182.log 190 B 2026-09-26 21:01:06
run-20260926-225105-181.log 153 B 2026-09-26 20:51:05
run-20260926-224104-180.log 153 B 2026-09-26 20:41:05
run-20260926-223103-179.log 153 B 2026-09-26 20:31:04
run-20260926-222103-178.log 190 B 2026-09-26 20:21:03
run-20260926-221102-177.log 153 B 2026-09-26 20:11:03
run-20260926-220101-176.log 190 B 2026-09-26 20:01:02
run-20260926-215100-175.log 153 B 2026-09-26 19:51:01
run-20260926-214100-174.log 153 B 2026-09-26 19:41:00
run-20260926-213059-173.log 153 B 2026-09-26 19:31:00
run-20260926-212058-172.log 153 B 2026-09-26 19:20:59
run-20260926-205455-171.log 133 KB 2026-09-26 19:10:58
run-20260926-202137-170.log 215 KB 2026-09-26 18:44:55
run-20260926-194055-169.log 257 KB 2026-09-26 18:11:37
run-20260926-191403-168.log 160 KB 2026-09-26 17:30:55
run-20260926-180537-167.log 281 KB 2026-09-26 17:04:03
run-20260926-170523-166.log 164 KB 2026-09-26 15:55:37
run-20260926-162230-165.log 178 KB 2026-09-26 14:55:23
run-20260926-154050-164.log 198 KB 2026-09-26 14:12:30
run-20260926-153049-163.log 153 B 2026-09-26 13:30:50
run-20260926-152049-162.log 153 B 2026-09-26 13:20:49
run-20260926-151048-161.log 153 B 2026-09-26 13:10:49
run-20260926-150047-160.log 153 B 2026-09-26 13:00:48
run-20260926-145046-159.log 153 B 2026-09-26 12:50:47
run-20260926-144046-158.log 153 B 2026-09-26 12:40:46
run-20260926-143045-157.log 153 B 2026-09-26 12:30:46
run-20260926-142044-156.log 153 B 2026-09-26 12:20:45
run-20260926-141044-155.log 153 B 2026-09-26 12:10:44
run-20260926-140043-154.log 153 B 2026-09-26 12:00:44
run-20260926-135042-153.log 190 B 2026-09-26 11:50:43
run-20260926-134042-152.log 153 B 2026-09-26 11:40:42
run-20260926-133041-151.log 153 B 2026-09-26 11:30:42
Tail — run-20260927-020118-200.log (last 200 lines)
FAIL: M9b the real tool still refuses the mid-sentence-only fixture (want rc=1 got=0)
FAIL: M9 counting occurrences again is caught (mutant exited 127, not runnable)
FAIL: M10 the unfenced mutant was built (missing or identical)
FAIL: M10b the real tool still refuses the fence-only fixture (want rc=1 got=0)
FAIL: M10 dropping the fence check in the counter is caught (mutant exited 127, not runnable)
queue-source-check suite: 109 passed, 13 failed
> build · mimo-v2.6-flash-free
$ cd /data/repo && git status --porcelain; echo "=== [0.4.79] entry:"; awk '/^## \[0.4.79\]/{f=1} f{print} f&&/^## \[0.4.78\]/{exit}' CHANGELOG.md | head -60; echo "=== heading count:"; grep -c '^## \[' CHANGELOG.md; grep '^## \[' CHANGELOG.md | tail -3
M agent-logs/PROGRESS.md
 M tests/test_queue_source.sh
 M tools/queue-source-check
=== [0.4.79] entry:
## [0.4.79] - 2026-09-26 — queue item (11): `tools/inbox-status` — the STEP 0 duty ("answer the investor BEFORE any other work") is now graded by a tool, not by the prose the run wrote about itself: both message DBs' unread rows plus every unhandled `INBOX.md` entry, exit `0/1/2/3`, read-only, bodies never printed — proved by a 91-assertion suite with 4 mutants and 1 leak witness

### What it grades
- **O1** — every `investor_to_agent` row in **both** `messages.db` files has `read=1`; **O2** — every `## ` entry in `INBOX.md` is struck `~~HANDLED` **in its heading** or carries a reply marker **in its body**. Both are reported every run, not just the first: `owed.unread_messages`, `owed.unreplied_entries`, `owed.total`.
- The rule being enforced is the oldest standing one in this repo — *"an unanswered investor is a failed run, regardless of other progress"* — which until now had **no witness at all**: the only evidence a run obeyed it was a sentence that run wrote into `agent-logs/PROGRESS.md`, graded by whoever read it next. `[0.4.71]`'s `investor-messages` dashboard row proved the *count* was 0 at some instant; nothing proved the *entries* were ever answered.
- Exit contract: `0` nothing owed · `1` duty open (`owed.total > 0`) · `2` bad args · `3` **cannot verify** — `INBOX.md` or a DB missing/unreadable, or the schema not the documented one. Exit 3 can never carry `ok: true`, and a file that was not read is never a pass.
- `exit_code` is also a field in the JSON, equal to the process exit code, so a machine consumer never re-derives the verdict from prose.

### The three decisions that could not be guesses
- **"Replied" is a pattern, because `INBOX.md` is not mine to structure.** The protocol forbids the agent from adding anything to that file except a `~~HANDLED~~` strikethrough, so the marker is read from the wording earlier runs already used: `REPLY SENT (dev 65 / prod 31)`, `Reply sent (dev 63 / prod 29)`, `reply dev 68 / prod 34` — i.e. the `agent_to_investor` row ids an insert actually produces. All three shapes are fixtures in the suite. A run that replies but cannot show it in the entry body fails here, which is the point: **the proof lives where the next run reads.**
- **Open-but-replied is allowed, deliberately.** The one currently-open entry (line 452, the six identity accounts) is blocked on `REPORT.md` §14, not unanswered. Folding "blocked" into "unanswered" would have made the tool cry wolf on its very first live run, and a check that is routinely red is one people learn to ignore. Executed-vs-blocked stays the human's `~~HANDLED~~` strike; the tool reports `open_replied` and `open_unreplied` separately and only the latter fails.
- **The strike is heading-scoped.** `INBOX.md` line 183's body *quotes* `~~HANDLED …~~` — counting the marker anywhere would close an entry nobody struck. The suite pins that exact shape (heading open, body quoting the marker → still exit 1).
- **Read-only, and it cannot discharge what it grades**: DBs open via `file:…?mode=ro` (a missing path errors instead of creating an empty database), no row is ever marked read, `INBOX.md` is never edited. Proven by hashing the fixture DB before/after and re-counting the unread row.
- **Message BODIES are never printed** — unread rows report `db`/`id`/`ts` only, JSON and human alike. The investor's text stays in the thread, not in a log that outlives it; with free-model providers retaining prompts, "no secrets in prompts" now extends to "no message bodies in run logs".

### Tests
- **`tests/test_inbox_status.sh` — 91 assertions, 4 mutants + 1 leak witness, hermetic (<2s)**: every scenario builds its own `INBOX.md` and its own SQLite fixtures (documented schema verbatim) and passes all three paths explicitly, so **no assertion depends on the live investor thread** — which the human controls and which could gain an unread row mid-run. The live repo is exercised only by the invariant `exit_code == process rc`, never by "the inbox happens to be clean today".
- Coverage: `--help`'s four exit codes, both obligations separately and together (`owed.total` 1 vs 2), the heading-scoped strike, all three reply-marker shapes, all four availability failures (missing inbox / missing dev db / missing prod db / wrong schema / no table), body-never-printed in **both** formats, read-only proof, `--format json` vs `human`, and invalid-argument exit 2.
- **Mutants M1–M4 must be caught**: never report the duty open · treat every open entry as replied · treat every heading as handled · let "cannot verify" become a pass. Each precondition asserts its needle occurs **exactly once** and that the mutant is **byte-different** (`cmp -s`) — the `[0.4.72]` lesson, applied before the first mutation. Non-vacuity is a **JSON-object** check, not merely "non-empty output": the first draft chmod'd nothing and every mutant exited **126** (permission denied), which the suite reported as four catches for four unrunnable files.
- **M5 is a witness, not a catch**: a purpose-built two-needle mutator (SELECT must fetch `body` **and** the projection must emit it — one needle alone yields an `IndexError` traceback and a green for the wrong reason) makes the tool print a body, and the suite asserts the fixture body **does** surface. That is what proves section 2's no-leak assertions have teeth: without it they would pass for an implementation that dumps every row.
- **Regression (authoritative)**: `./tools/regression-run --format json` → **51 suites, 4145 passed, 0 failed, 0 skipped, exit 0**. Closure is arithmetic: **50 + 1 = 51** suites and **4054 + 91 = 4145** — exactly this suite added, not one assertion moved in any other suite. `regression-run --list` discovers it (51); `python3 -m py_compile` and `bash -n` clean on both new files.

### Queue -> agent-logs/PROGRESS.md
Authoritative next-candidate queue lives in `agent-logs/PROGRESS.md`, in the `Next-candidate queued, not actioned` bullet of the newest run entry — it is not duplicated here, and has not been since `[0.4.67]`. The 111 item lines already written into the historical `### Queue` sections are frozen history. To read the live queue, open `PROGRESS.md`; to know whether that is still true, run `tools/queue-source-check`.

### Notes
- **Scope, deliberately one question**: item (11) asked for a tool that makes the STEP 0 obligation machine-checkable. Delivered exactly that: unhandled count, replied-but-open list, non-zero exit when a reply was never sent. It does **not** judge execution, does **not** mark anything read, and does **not** touch `system-status` — no dashboard check was added, so the check count is untouched.
- **Why `--repo` does not move the databases**: message DBs are runtime state deliberately outside the pushed repo, so deriving them from `--repo` would point a sandbox run at the live investor thread. Only `--dev-db`/`--prod-db` or their env hooks move them; `--repo` only derives `INBOX.md`.
- **Live result at commit time**: `73 entries, 72 handled, 1 open+replied (line 452)`, `0 unread dev / 0 prod`, `last_reply dev 76 / prod 42`, exit **0**.
- **Placement**: parked at the bottom like `[0.4.29]`–`[0.4.78]`, so `## [0.4.28]` stays the top entry and `GLADEX_APP_VERSION` (`0.4.28`) keeps agreeing with it; pointer-only `### Queue` so `queue-source-check` stays green.
- **Safety**: model spend **0.00** (`*-free` only), **no money moved** (`BUDGET.md` untouched: **1.50 spent / 3.50 remaining**), **zero DNS writes** (no `pdns-api.py` call), no paid API key configured, **no secret read or printed** (`/root/.pdns-token` untouched; no credential, and now no message body, in any prompt/thread/commit), **no service restarted, no certificate touched, no promote executed, Docker stacks and both investor apps untouched**. The only live I/O: the read-only probes in the STEP-0 reply, `--help` runs, and the tool's own `mode=ro` reads of the two message DBs.
=== heading count:
84
## [0.4.77] - 2026-09-26 — the fix for three identical defects was a review comment: nothing stopped a SECOND raw `$(dig …)` reader from entering `tools/system-status`, so the invariant is now a test — this tool executes `dig` in exactly one place, inside `dns_query()`, and a second reader, a deleted one, one outside that function, or one without its errexit guard fails the suite
## [0.4.78] - 2026-09-26 — queue item (21): the dig transport-shape audit of the last three dig readers — measured the real failure shape ONCE (`;;` lines on **stdout**, **0 bytes on stderr**, rc 9) and found that every suite claiming to cover these tools had planted that shape on **stderr**, i.e. none of them had ever tested what dig actually does; the invariant *"a `;;` line is never a value, and its presence means the query did not complete"* is now enforced in `dns-verify`, `domain-availability-check` and `ip-drift-check`, and proved by a new 80-assertion suite
## [0.4.79] - 2026-09-26 — queue item (11): `tools/inbox-status` — the STEP 0 duty ("answer the investor BEFORE any other work") is now graded by a tool, not by the prose the run wrote about itself: both message DBs' unread rows plus every unhandled `INBOX.md` entry, exit `0/1/2/3`, read-only, bodies never printed — proved by a 91-assertion suite with 4 mutants and 1 leak witness
> build · mimo-v2.6-flash-free
$ cd /data/repo && python3 - <<'PY'
import re
lines=open('agent-logs/PROGRESS.md',encoding='utf-8').read().split('\n')
print('raw occurrences:', sum(l.count('Next-candidate queued') for l in lines))
start=re.compile(r'^\s*(?:[-*+]\s+)?(?:\*\*)?Next-candidate queued')
print('entry lines:', sum(1 for l in lines if start.match(l)))
print('mid-line quotes:', sum(1 for l in lines if 'Next-candidate queued' in l and not start.match(l)))
PY
raw occurrences: 56
entry lines: 49
mid-line quotes: 3
> build · mimo-v2.6-flash-free
$ cd /data/repo && cat >> CHANGELOG.md <<'EOF'

## [0.4.80] - 2026-09-27 — queue item (9): `queue-source-check` reported **`N PROGRESS bullet(s)`** but counted raw *occurrences* of the phrase — 56 against the **49** lines that actually open an entry, a number that moved whenever an entry merely quoted it — so `progress_bullets` now counts entry lines (marker optional, once per line, outside fences), and the suite's own M8 needle turned out to be silently retargeted by this very change

### The defect, measured rather than described
- `build_report()` read `bullets = pg_text.count("Next-candidate queued")` and published it as `rules.progress_bullets` and in the detail string `… 56 PROGRESS bullet(s)`. Measured on the live tree at the time of the fix: **56 raw occurrences / 49 entry lines / 3 mid-line quotations / 52 lines containing the phrase**. The field's name and its human line both say *bullet*; nothing counted bullets.
- Why it is a defect and not a rounding difference: the count **moves for the wrong reason**. Writing an entry that *quotes* the phrase — the R5 prose itself (`- **The gate, tools/queue-source-check** … Next-candidate queued …`), a dashboard detail line, a health summary — all three are mid-line mentions in `PROGRESS.md` today, and each adds one to a number described as a bullet count. `[0.4.67]` made `PROGRESS.md` the single authoritative queue precisely so that its shape could be read mechanically; a reading that cannot distinguish an entry from a mention is the hand-maintained-copy failure mode in a new costume.
- **R5's verdict did not move and could not**: it only requires `>= 1`, so every fixture that passed before still passes — the fix changes a *reported* number, and the suite was extended with the cases where the two readings genuinely disagree rather than with a new rule.

### What counts as an entry (the whole contract, now in the docstring)
- A line whose **first non-space text is the phrase**, with or without a list marker: `- **Next-candidate queued…**` counts, and so does the bare `**Next-candidate queued…**` paragraph form six historical entries were written in — because R5 asks whether the authoritative list still *exists*, and a queue entry written without its `- ` is still an entry. **Once per line**: a bullet that repeats the phrase later in the same line counts once.
- A **mid-line mention** never counts — the three live ones are excluded — and a **fenced block** is not structure, the same rule `parse()` already applies to `CHANGELOG.md` (PROGRESS's own fences hold no mention today, so fence-awareness costs nothing now and cannot be surprised later).
- `progress_bullets` (key name, pinned by `A20`'s 8-key set) and the `PROGRESS bullet(s)` detail wording are **unchanged** — only what they count changed, and `count_queue_bullets()` now owns that decision in one place with its reasoning in the docstring.

### The suite's own mutation needle was silently retargeted by this change — caught, then made impossible
- `M8` disables `parse()`'s fence check with the needle `        if line.lstrip().startswith("```"):` and `str.replace(old, new, 1)`. This step added a **second, byte-identical line** inside the new `count_queue_bullets()`, which sits earlier in the file — so `replace(…, 1)` quietly edited the new function (a function M8's fixture never exercises) and **`M8` went green against a `parse()` that still had its guard**: `106 passed, 1 failed` on the first run of the edited suite, with the failure being M8 itself.
- Fixed the way the repo has been fixing this class since `[0.4.72]`: the needle is **anchored to `parse()`'s own comment** and asserted **`t.count(old) == 1`** before the replacement, with the reason written at the needle. A mutation whose target can drift is not a mutation test; it is a coin flip that happened to land heads for months.
- The same run hardened `M8`, `M9`, `M10`'s build checks to `[ -f "$MUT" ] && ! cmp -s …`: a python `assert` that fails writes **no file**, `cmp` against a missing path says "different", and the old condition then reported *built* for a mutant that does not exist — the exact vacuity `[0.4.79]` caught in `inbox-status`'s non-vacuity guard (126 = unrunnable, reported as caught).

### Tests
- **`tests/test_queue_source.sh` 107 → 122 assertions, still 8 mutants (M1–M7 + M8, now M9/M10)**, hermetic, no live repo read: new section **L** builds one fixture whose PROGRESS holds all four shapes at once (bullet, markerless entry, mid-sentence quotation, second occurrence on the counted line, fenced quotation) and asserts **`progress_bullets = 3`** — where the old reading answers **6**, so the number itself distinguishes the two contracts; plus a mid-sentence-only repo (→ `0`, exit 1, `authoritative queue is gone`) and a fence-only repo (→ `0`, exit 1).
- **M9** reverts `count_queue_bullets(pg_text)` → `pg_text.count(...)` and must exit **0** where the real tool exits **1** on the mid-sentence fixture; **M10** drops the fence check inside the counter and must exit **0** where the real tool exits **1** on the fence-only fixture. Both needles are uniqueness-asserted, and both builds distinguish *missing* / *identical* / *evaded* / *caught* instead of collapsing them into one branch.
- **Pre-fix replay (the suite is only worth what it fails on)**: `HEAD`'s `tools/queue-source-check` under this suite → **109 passed / 13 failed**, including `L2 … (want 3 got 6)` and both mutants' `not runnable` — i.e. the old tool cannot satisfy the new contract, stated by the tool's own output rather than by prose.
- **Regression (authoritative)**: `./tools/regression-run --format json` → **52 suites, 4267 passed, 0 failed, 0 skipped, exit 0** — closure arithmetic below.

### Queue -> agent-logs/PROGRESS.md
Authoritative next-candidate queue lives in `agent-logs/PROGRESS.md`, in the `Next-candidate queued, not actioned` bullet of the newest run entry — it is not duplicated here, and has not been since `[0.4.67]`. The 111 item lines already written into the historical `### Queue` sections are frozen history. To read the live queue, open `PROGRESS.md`; to know whether that is still true, run `tools/queue-source-check`.

### Notes
- **Scope, deliberately one question**: item (9) says the field counts a number its name does not describe. Delivered exactly that: same key, same detail wording, same `>= 1` rule, count that means what it says. No rule was added, no exit code changed, `system-status`'s `queue-source` row keeps reading the same `detail` string, and **no dashboard check count moved** (still 35).
- **Live result at commit time**: `queue-source-check` → `OK - one queue: [0.4.79] pointer-only, 111 item line(s) frozen across 37 section(s), 49 PROGRESS bullet(s)`, exit **0** (was `56 PROGRESS bullet(s)` for the same tree).
- **Placement**: parked at the bottom like `[0.4.29]`–`[0.4.79]`, so `## [0.4.28]` stays the top entry and `GLADEX_APP_VERSION` (`0.4.28`) keeps agreeing with it; pointer-only `### Queue` so `queue-source-check` stays green (an entry that listed items here would fail `R3` in the same run that wrote it).
- **Safety**: model spend **0.00** (`*-free` only), **no money moved** (`BUDGET.md` untouched: **1.50 spent / 3.50 remaining**), **zero DNS writes** (no `pdns-api.py` call), no paid API key configured, **no secret read or printed** (`/root/.pdns-token` untouched; no credential and no message body in any prompt/thread/commit), **no service restarted, no certificate touched, no promote executed** (the stale reviewer verdict still refuses it), **Docker stacks and both investor apps untouched**. The only live I/O: the read-only probes in the STEP-0 reply, `--help` runs, and the read-only checks the live dashboard performs during regression.
EOF
grep -c '^## \[' CHANGELOG.md; grep '^## \[' CHANGELOG.md | tail -1 | cut -c1-80; ./tools/queue-source-check; echo rc=$?
85
## [0.4.80] - 2026-09-27 — queue item (9): `queue-source-check` reported **`N 
queue-source-check: OK - one queue: [0.4.80] pointer-only, 111 item line(s) frozen across 38 section(s), 49 PROGRESS bullet(s)
rc=0
> build · mimo-v2.6-flash-free
← Edit repo/CHANGELOG.md
Index: repo/CHANGELOG.md
===================================================================
--- repo/CHANGELOG.md
+++ repo/CHANGELOG.md
@@ -2398,7 +2398,7 @@
 Authoritative next-candidate queue lives in `agent-logs/PROGRESS.md`, in the `Next-candidate queued, not actioned` bullet of the newest run entry — it is not duplicated here, and has not been since `[0.4.67]`. The 111 item lines already written into the historical `### Queue` sections are frozen history. To read the live queue, open `PROGRESS.md`; to know whether that is still true, run `tools/queue-source-check`.
 
 ### Notes
 - **Scope, deliberately one question**: item (9) says the field counts a number its name does not describe. Delivered exactly that: same key, same detail wording, same `>= 1` rule, count that means what it says. No rule was added, no exit code changed, `system-status`'s `queue-source` row keeps reading the same `detail` string, and **no dashboard check count moved** (still 35).
-- **Live result at commit time**: `queue-source-check` → `OK - one queue: [0.4.79] pointer-only, 111 item line(s) frozen across 37 section(s), 49 PROGRESS bullet(s)`, exit **0** (was `56 PROGRESS bullet(s)` for the same tree).
+- **Live result at commit time**: `queue-source-check` → `OK - one queue: [0.4.80] pointer-only, 111 item line(s) frozen across 38 section(s), 49 PROGRESS bullet(s)`, exit **0** (was `56 PROGRESS bullet(s)` for the same tree).
 - **Placement**: parked at the bottom like `[0.4.29]`–`[0.4.79]`, so `## [0.4.28]` stays the top entry and `GLADEX_APP_VERSION` (`0.4.28`) keeps agreeing with it; pointer-only `### Queue` so `queue-source-check` stays green (an entry that listed items here would fail `R3` in the same run that wrote it).
 - **Safety**: model spend **0.00** (`*-free` only), **no money moved** (`BUDGET.md` untouched: **1.50 spent / 3.50 remaining**), **zero DNS writes** (no `pdns-api.py` call), no paid API key configured, **no secret read or printed** (`/root/.pdns-token` untouched; no credential and no message body in any prompt/thread/commit), **no service restarted, no certificate touched, no promote executed** (the stale reviewer verdict still refuses it), **Docker stacks and both investor apps untouched**. The only live I/O: the read-only probes in the STEP-0 reply, `--help` runs, and the read-only checks the live dashboard performs during regression.

> build · mimo-v2.6-flash-free
$ cd /data/repo && grep -n "^## queue-source-check" tools/REGISTRY.md; awk '/^## queue-source-check/{f=1} f&&/^## /&&!/queue-source-check/{exit} f{print}' tools/REGISTRY.md | head -80
2062:## queue-source-check
## queue-source-check

**Purpose**: Prove that the next-candidate work queue has **exactly one home**.
`agent-logs/PROGRESS.md` carries the live list; CHANGELOG's newest `### Queue`
section is a POINTER at it; the 111 item lines already written into the 25
historical sections are frozen history. It exists because the alternative — two
hand-maintained copies — had already drifted: `PROGRESS.md`'s `0.4.65` queue
listed the regression-baseline item that `0.4.66` then actioned, while this
file's `0.4.65` queue never listed it and instead carried a rejected `--jobs`
note. The same version carried two different queues, so an item could sit in one
and be re-proposed from the other, and nothing compared them for eleven
versions. That is the class this repo keeps naming one level up: **two sources
for one number with no versioned arbiter** — the fix is one source plus a gate,
not a third copy of the list.

**Location**: `/data/repo/tools/queue-source-check`

**Usage**:
```
queue-source-check [--format human|json] [--repo DIR]
```

**Options**:
- `--format human|json` - Output format (default: `human`; `json` = one compact object)
- `--repo DIR` - Repo to check (default: `GLADEX_REPO_DIR`, else the repo this
  script lives in). Resolves the script's own path with `realpath` on the FILE
  first, so a symlinked invocation finds the target's repo and not the link's
  parent — the trap `source-sync-check` pins for itself.
- `--help` - Show help (the epilog's exit-code block is derived from the
  module docstring, so `--help` and the source cannot disagree)

**Exit codes**:
- `0` - Single source verified
- `1` - A violation: a second list exists, the pointer was dropped, or the
  authoritative list is empty (every violation is in `violations[]`, not just the
  first)
- `2` - Invalid arguments (and under `--format json`, a JSON object on stdout
  with an empty stderr — never empty stdin for a `| jq` consumer)
- `3` - Cannot verify: `CHANGELOG.md`, `agent-logs/PROGRESS.md` or any
  `### Queue` section is absent. **Not a pass, never reported as one**

**Rules** (availability → exit 3, short-circuiting; the rest all evaluated, all
reported → exit 1):

| | rule |
|---|---|
| A1–A3 | `CHANGELOG.md` readable, it owns ≥ 1 `### Queue` section, `agent-logs/PROGRESS.md` readable |
| R1 | total `- ` item lines across every `### Queue` section **== 111** |
| R2 | the **newest** `## [x.y.z]` entry owns a `### Queue` section |
| R3 | that section contains **no list item of any kind** (`-`, `*`, `+`, `1.`) |
| R4 | that section names `agent-logs/PROGRESS.md` |
| R5 | `agent-logs/PROGRESS.md` carries ≥ 1 `Next-candidate queued` bullet |

**Design**:
- **R1 is an equality against a constant, not a floor.** A count that may only
  grow cannot see a history rewrite; changing `FROZEN_ITEMS` is a deliberate edit
  made in the entry that intends it.
- **All rules report.** Three broken rules produce three lines — a tool that
  reports one defect at a time turns three fixes into three runs. This is also
  the only property that catches a truncating mutant (it exits 1 either way).
- **Fenced code blocks are not structure.** An entry that *quotes* the shape of a
  `### Queue` section (as `[0.4.67]` does, twice) must not gain a section from
  the quotation, and quoted `- ` lines are not in the freeze.
- **`detail` is sanitised** (no `"`, no `\`) so a shell caller can drop it into a
  hand-built JSON row without re-escaping — `system-status` builds its rows as
  strings and one quote there would invalidate the whole dashboard's JSON.
- **The channel is read by a throwaway `ArgumentParser` carrying only
  `--format`**, so it survives the very argv that broke the full parse; where no
  format is readable (`--format yaml`, anything after `--`) the channel stays
  human and no JSON is invented.
- **`--format json --help` prints human help and exits 0** — parity with
  `repo-lint` and `regression-run`, and deliberately *not* fixed here: that is a
  carried repo-wide queue item, and changing it for one tool would make three
  tools disagree instead of two. Pinned by `H9`–`H11`.

**JSON**: one object, **11 keys** — `tool, timestamp, repo_root, changelog,
progress, rules, violations, error, detail, ok, exit_code` — with `rc ==
exit_code`. `rules` carries **8 keys**: `frozen_items_expected,
frozen_items_actual, queue_sections, newest_version, newest_has_queue_section,
newest_queue_items, newest_points_at_progress, progress_bullets`. The usage

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)
[Sun Sep 27 01:33:55 2026] 127.0.0.1:36216 Accepted
[Sun Sep 27 01:33:55 2026] 127.0.0.1:36216 Closing
[Sun Sep 27 01:33:55 2026] 127.0.0.1:36218 Accepted
[Sun Sep 27 01:33:55 2026] 127.0.0.1:36218 Closing
[Sun Sep 27 01:33:56 2026] 127.0.0.1:36228 Accepted
[Sun Sep 27 01:33:56 2026] 127.0.0.1:36228 Closing
[Sun Sep 27 01:33:56 2026] 127.0.0.1:36236 Accepted
[Sun Sep 27 01:33:56 2026] 127.0.0.1:36236 Closing
[Sun Sep 27 01:33:56 2026] 127.0.0.1:36250 Accepted
[Sun Sep 27 01:33:56 2026] 127.0.0.1:36250 Closing
[Sun Sep 27 01:33:57 2026] 127.0.0.1:36262 Accepted
[Sun Sep 27 01:33:57 2026] 127.0.0.1:36262 Closing
[Sun Sep 27 01:33:57 2026] 127.0.0.1:36278 Accepted
[Sun Sep 27 01:33:57 2026] 127.0.0.1:36278 Closing
[Sun Sep 27 01:33:57 2026] 127.0.0.1:36286 Accepted
[Sun Sep 27 01:33:57 2026] 127.0.0.1:36286 Closing
[Sun Sep 27 01:37:17 2026] 127.0.0.1:35272 Accepted
[Sun Sep 27 01:37:17 2026] 127.0.0.1:35272 Closing
[Sun Sep 27 01:39:03 2026] 127.0.0.1:48730 Accepted
[Sun Sep 27 01:39:03 2026] 127.0.0.1:48730 Closing
[Sun Sep 27 01:51:46 2026] 127.0.0.1:53810 Accepted
[Sun Sep 27 01:51:46 2026] 127.0.0.1:53810 Closing
[Sun Sep 27 01:59:44 2026] 127.0.0.1:46670 Accepted
[Sun Sep 27 01:59:44 2026] 127.0.0.1:46670 Closing
[Sun Sep 27 02:03:06 2026] 127.0.0.1:42958 Accepted
[Sun Sep 27 02:03:06 2026] 127.0.0.1:42958 Closing
[Sun Sep 27 02:03:06 2026] 127.0.0.1:42968 Accepted
[Sun Sep 27 02:03:06 2026] 127.0.0.1:42968 Closing
[Sun Sep 27 02:03:06 2026] 127.0.0.1:42974 Accepted
[Sun Sep 27 02:03:06 2026] 127.0.0.1:42974 Closing
[Sun Sep 27 02:03:06 2026] 127.0.0.1:42976 Accepted
[Sun Sep 27 02:03:06 2026] 127.0.0.1:42976 Closing
[Sun Sep 27 02:03:06 2026] 127.0.0.1:42992 Accepted
[Sun Sep 27 02:03:06 2026] 127.0.0.1:42992 Closing
[Sun Sep 27 02:03:06 2026] 127.0.0.1:43004 Accepted
[Sun Sep 27 02:03:06 2026] 127.0.0.1:43004 Closing
[Sun Sep 27 02:03:06 2026] 127.0.0.1:43008 Accepted
[Sun Sep 27 02:03:07 2026] 127.0.0.1:43008 Closing
[Sun Sep 27 02:03:07 2026] 127.0.0.1:43022 Accepted
[Sun Sep 27 02:03:07 2026] 127.0.0.1:43022 Closing
[Sun Sep 27 02:03:07 2026] 127.0.0.1:43026 Accepted
[Sun Sep 27 02:03:07 2026] 127.0.0.1:43026 Closing
[Sun Sep 27 02:03:07 2026] 127.0.0.1:43036 Accepted
[Sun Sep 27 02:03:07 2026] 127.0.0.1:43036 Closing
[Sun Sep 27 02:04:28 2026] 127.0.0.1:43138 Accepted
[Sun Sep 27 02:04:28 2026] 127.0.0.1:43138 Closing
[Sun Sep 27 02:04:46 2026] 127.0.0.1:34382 Accepted
[Sun Sep 27 02:04:46 2026] 127.0.0.1:34382 Closing
[Sun Sep 27 02:04:46 2026] 127.0.0.1:34384 Accepted
[Sun Sep 27 02:04:46 2026] 127.0.0.1:34384 Closing
[Sun Sep 27 02:05:34 2026] 127.0.0.1:34880 Accepted
[Sun Sep 27 02:05:34 2026] 127.0.0.1:34880 Closing
[Sun Sep 27 02:05:59 2026] 127.0.0.1:55176 Accepted
[Sun Sep 27 02:05:59 2026] 127.0.0.1:55176 Closing
[Sun Sep 27 02:19:16 2026] 127.0.0.1:60940 Accepted
[Sun Sep 27 02:19:16 2026] 127.0.0.1:60940 Closing
[Sun Sep 27 02:19:35 2026] 127.0.0.1:46012 Accepted
[Sun Sep 27 02:19:35 2026] 127.0.0.1:46012 Closing
[Sun Sep 27 02:19:36 2026] 127.0.0.1:46026 Accepted

Generated 2026-09-27 00:19:36 UTC · Gladex.de