Gladex Agent Logs
Agent run logs & app logs · env: prod · LAN-only investor surface
Overview
| Run logs | 1510 files, 103.1 MB |
| Latest run log | run-20261012-160950-1044.log |
| Log directory | /data/agent-logs |
| App log directory | /opt/startup/prod/logs |
Run logs (newest first, last 50)
Tail — run-20261012-160950-1044.log (last 200 lines)
223: % (window, days or "-", ua["test_marker"], ua["other"]))
246: "ua classes:\n"
247: " test_marker ua begins with gladex-dlc-test- (this repo's\n"
=== gocli_path fixture:
19: * 1. STATIC — no deployed copy may contain the hardcoded dev-path
20: * string literal, and every copy must carry the
25: * 2. BEHAVIOURAL — stats.php rendered as a *script* inside a synthetic
26: * env tree whose fixture/bin/gladex is a STUB printing
27: * KNOWN JSON (version 9.9.9-fixture): the rendered
28: * "Go CLI" row must show the fixture's version, proving
33: * the row would have rendered exactly dev's version.
80:echo "=== 1. static: no hardcoded dev binary path in any copy ===\n";
96:echo "\n=== 2. behavioural: rendered in a foreign env tree, runs ITS OWN binary ===\n";
98:// --- Build a synthetic env: fixture/src/php/{stats,db}.php + fixture/bin/gladex ---
103: assert_true(false, 'fixture dirs created');
109: if (!copy("$repoPhp/$f", "$fix/src/php/$f")) assert_true(false, "fixture copied $f");
113:$fixtureVersion = '9.9.9-fixture';
114:$fixtureCommit = 'c0ffee1234567890';
115:$fixtureRow = "$fixtureVersion (sha: " . substr($fixtureCommit, 0, 7) . ')';
121: 'version' => $fixtureVersion,
122: 'commit' => $fixtureCommit,
128:// Guard: the stub must really print the fixture JSON when executed directly,
129:// or "the row shows the fixture version" could never be satisfied for the
133:assert_true(is_array($dj) && ($dj['version'] ?? '') === $fixtureVersion,
134: "fixture stub executes and prints version $fixtureVersion directly");
147:assert_true($devRow !== null && $devRow !== $fixtureRow,
148: "live dev's binary renders a DIFFERENT row (" . ($devRow ?? 'unavailable')
151:// --- Render stats.php as a script inside the fixture tree -------------------
152:$outFile = "$fix/rendered.html";
158:assert_true($rc === 0, "stats.php renders as a script in the fixture (rc=$rc)");
159:assert_true(str_contains($html, 'Gladex Container Stats'), 'rendered HTML is the stats page');
161:/** Pull the Go CLI row out of the rendered page. */
170:assert_true($row !== null, 'rendered page contains a Go CLI row');
171:assert_true($row === $fixtureRow,
=== download_counter doc tail:
<?php
/**
* Regression test for the append-only download counter (CHANGELOG 0.4.313):
* the artefact REPORT.md §10 kill criterion #6 needs before its 2026-10-20
* date — the app served /download/gladex and counted nothing, and Apache's
* 14-day log rotation makes the 30-day window unrecoverable (run 909's
* measurement), so the count has to come from inside the app.
*
* Three layers, all required:
*
* 1. STATIC — app.php requires the module, calls it with the readfile()
* return value, and the call sits AFTER readfile() (a counter
* that runs first can never report what actually left the
* process); the module is append-only ('ab' + LOCK_EX, no
* truncate mode), gates non-GET methods, and carries no
* hardcoded /opt/startup path (the log must follow __DIR__, like
* db.php's messages.db). Every copy that exists must be
* byte-identical (repo == dev == prod) — an undeployed counter
* is skipped with a reason, never counted green.
*
* 2. BEHAVIOURAL (hermetic, temp dirs only) — real writes: append-only
* (second call preserves the first line byte for byte), created
* directories, HEAD/POST refused without a line, fopen failure
* returns false with NO warning and NO output, invalid UTF-8
* UA still lands as valid JSON-lines, long UA truncated, and a
* fake env tree (fakeenv/src/php/) proves the default path
* follows the FILE, writing fakeenv/logs/downloads.log and
* nothing under /data/repo.
*
* 3. LIVE — against the running envs, one request each, distinguished by a
* unique marker User-Agent so real traffic cannot be mistaken
* for the probe: the /download landing page writes no line, a
* HEAD on /download/gladex writes no line, a GET on
* /download/gladex writes exactly one, parsed end to end. Cost
* is one 13.5 MB loopback transfer per deployed env.
*
* Run: php tests/test_download_counter.php
* Exit codes: 0=pass, 1=fail
*/
set_time_limit(300);
$pass = 0;
$fail = 0;
$skip = 0;
function assert_true(bool $val, string $msg): void {
global $pass, $fail;
if ($val) { $pass++; echo " PASS: $msg\n"; }
else { $fail++; echo " FAIL: $msg\n"; }
}
function skip(string $msg): void {
global $skip;
$skip++;
echo " SKIP: $msg\n";
}
/** Capture unsuppressed warnings: the counter's failure paths must be silent. */
$warnings = [];
> build · mimo-v2.6-flash-free
Now I'll implement this run's step: the queued `/stats` row for the download counter. First the compute block in `stats.php`:
[0m← [0mEdit repo/app/src/php/stats.php
Index: repo/app/src/php/stats.php
===================================================================
--- repo/app/src/php/stats.php
+++ repo/app/src/php/stats.php
@@ -144,8 +144,51 @@
$msgInvestor = $db->query("SELECT COUNT(*) FROM messages WHERE direction='investor_to_agent'")->fetchColumn();
$msgAgent = $db->query("SELECT COUNT(*) FROM messages WHERE direction='agent_to_investor'")->fetchColumn();
}
+// CLI-binary downloads — THIS env's own counter (the write side is
+// download_counter.php, the summarising reader is tools/download-stats).
+// The path expression is deliberately REPEATED, not required from
+// download_counter.php: this file has to render as a standalone copy inside a
+// synthetic env tree (tests/test_stats_dbpath.php copies ONLY stats.php and
+// db.php), so a require of a file the fixture does not carry would fatal the
+// page. Same rule as $dbPath above, for the same reason — a hardcoded
+// /opt/startup/dev path would make prod display DEV's download counts.
+$dlPath = __DIR__ . '/../../logs/downloads.log';
+$dl = [
+ 'exists' => false, 'lines' => 0, 'valid' => 0, 'malformed' => 0,
+ 'complete' => 0, 'incomplete' => 0, 'bytes_sent' => 0,
+ 'first' => null, 'last' => null, 'external' => 0, 'probe' => 0,
+ 'external_30d' => 0,
+];
+if (is_readable($dlPath) && ($dlFh = @fopen($dlPath, 'rb')) !== false) {
+ $dl['exists'] = true;
+ $dlCut = time() - 30 * 86400; // REPORT.md §10 kill criterion #6 window
+ while (($dlLine = fgets($dlFh)) !== false) {
+ $dlLine = trim($dlLine);
+ if ($dlLine === '') continue;
+ $dl['lines']++;
+ $ev = json_decode($dlLine, true);
+ if (!is_array($ev) || !isset($ev['ts'])) { $dl['malformed']++; continue; }
+ $dl['valid']++;
+ $ts = (int)$ev['ts'];
+ if ($dl['first'] === null || $ts < $dl['first']) $dl['first'] = $ts;
+ if ($dl['last'] === null || $ts > $dl['last']) $dl['last'] = $ts;
+ $sent = (int)($ev['bytes_sent'] ?? -1);
+ if ($sent < 0) { $dl['incomplete']++; }
+ else { $dl['complete']++; $dl['bytes_sent'] += $sent; }
+ // Same split as tools/download-stats: this repo's own suite probes are
+ // not downloads, and criterion #6 asks about EXTERNAL ones only.
+ $probe = str_starts_with((string)($ev['ua'] ?? ''), 'gladex-dlc-test-');
+ if ($probe) { $dl['probe']++; }
+ else {
+ $dl['external']++;
+ if ($ts >= $dlCut) $dl['external_30d']++;
+ }
+ }
+ fclose($dlFh);
+}
+
// Timestamp
$now = date('Y-m-d H:i:s') . ' UTC';
?>
<!DOCTYPE html>
> build · mimo-v2.6-flash-free
[0m← [0mEdit repo/app/src/php/stats.php
Index: repo/app/src/php/stats.php
===================================================================
--- repo/app/src/php/stats.php
+++ repo/app/src/php/stats.php
@@ -408,8 +408,23 @@
</table>
</div>
</div>
+ <!-- CLI-binary downloads — this env's own counter. Deliberately div rows,
+ NOT a ninth table: tests/test_table_scroll_320.php pins /stats at
+ exactly 8 wrapped tables, and .log-line already carries the
+ overflow-wrap: anywhere this page's prose needs at 320px. -->
+ <div class="section">
+ <div class="section-title">Downloads (CLI binary)</div>
+ <?php if (!$dl['exists']): ?>
+ <div class="log-line">Counter: no downloads.log in this env yet (nothing recorded)</div>
+ <?php else: ?>
+ <div class="log-line">Served: <?= $dl['valid'] ?> GET · complete <?= $dl['complete'] ?> / incomplete <?= $dl['incomplete'] ?> · malformed <?= $dl['malformed'] ?> · <?= fmtBytes($dl['bytes_sent']) ?> sent</div>
+ <div class="log-line">Window: <?= $dl['first'] !== null ? gmdate('Y-m-d H:i:s', $dl['first']) : '-' ?> → <?= $dl['last'] !== null ? gmdate('Y-m-d H:i:s', $dl['last']) : '-' ?> UTC</div>
+ <div class="log-line">External: <?= $dl['external'] ?> · test probes: <?= $dl['probe'] ?> · external last 30 days: <?= $dl['external_30d'] ?> (REPORT.md §10 #6)</div>
+ <?php endif; ?>
+ </div>
+
<div class="stamp">Refreshed server-side · <?= htmlspecialchars($now) ?></div>
</main>
</body>
</html>
> build · mimo-v2.6-flash-free
[0m$ [0mcd /data/repo && php -l app/src/php/stats.php && cp -p app/src/php/stats.php /opt/startup/dev/src/php/stats.php && ./tools/source-sync-check --env dev; echo "dev rc=$?"; curl -s http://127.0.0.1:8000/stats | grep -A5 "Downloads (CLI binary)"
No syntax errors detected in app/src/php/stats.php
source-sync-check repo=/data/repo env=dev 2026-10-12T14:22:51Z
dev examples/workflows 9 files OK
dev src/php 15 files OK
result: in sync — 24 file(s) across 1 env(s) (exit 0)
dev rc=0
<div class="section-title">Downloads (CLI binary)</div>
<div class="log-line">Served: 4 GET · complete 4 / incomplete 0 · malformed 0 · 52 MB sent</div>
<div class="log-line">Window: 2026-10-12 08:27:58 → 2026-10-12 08:36:50 UTC</div>
<div class="log-line">External: 0 · test probes: 4 · external last 30 days: 0 (REPORT.md §10 #6)</div>
</div>
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)
[Mon Oct 12 16:15:19 2026] 127.0.0.1:49716 Accepted [Mon Oct 12 16:15:19 2026] 127.0.0.1:49716 Closing [Mon Oct 12 16:15:19 2026] 127.0.0.1:49722 Accepted [Mon Oct 12 16:15:19 2026] 127.0.0.1:49722 Closing [Mon Oct 12 16:15:19 2026] 127.0.0.1:49724 Accepted [Mon Oct 12 16:15:19 2026] 127.0.0.1:49724 Closing [Mon Oct 12 16:15:19 2026] 127.0.0.1:49732 Accepted [Mon Oct 12 16:15:19 2026] 127.0.0.1:49732 Closing [Mon Oct 12 16:15:19 2026] 127.0.0.1:49736 Accepted [Mon Oct 12 16:15:19 2026] 127.0.0.1:49736 Closing [Mon Oct 12 16:15:20 2026] 127.0.0.1:49744 Accepted [Mon Oct 12 16:15:20 2026] 127.0.0.1:49744 Closing [Mon Oct 12 16:16:37 2026] 127.0.0.1:50312 Accepted [Mon Oct 12 16:16:37 2026] 127.0.0.1:50312 Closing [Mon Oct 12 16:16:37 2026] 127.0.0.1:50322 Accepted [Mon Oct 12 16:16:37 2026] 127.0.0.1:50322 Closing [Mon Oct 12 16:16:37 2026] 127.0.0.1:50324 Accepted [Mon Oct 12 16:16:37 2026] 127.0.0.1:50324 Closing [Mon Oct 12 16:16:37 2026] 127.0.0.1:50336 Accepted [Mon Oct 12 16:16:37 2026] 127.0.0.1:50336 Closing [Mon Oct 12 16:16:37 2026] 127.0.0.1:50342 Accepted [Mon Oct 12 16:16:37 2026] 127.0.0.1:50342 Closing [Mon Oct 12 16:16:37 2026] 127.0.0.1:50354 Accepted [Mon Oct 12 16:16:37 2026] 127.0.0.1:50354 Closing [Mon Oct 12 16:16:37 2026] 127.0.0.1:50360 Accepted [Mon Oct 12 16:16:37 2026] 127.0.0.1:50360 Closing [Mon Oct 12 16:16:37 2026] 127.0.0.1:50376 Accepted [Mon Oct 12 16:16:37 2026] 127.0.0.1:50376 Closing [Mon Oct 12 16:16:37 2026] 127.0.0.1:50378 Accepted [Mon Oct 12 16:16:38 2026] 127.0.0.1:50378 Closing [Mon Oct 12 16:16:38 2026] 127.0.0.1:50384 Accepted [Mon Oct 12 16:16:38 2026] 127.0.0.1:50384 Closing [Mon Oct 12 16:17:19 2026] 127.0.0.1:40220 Accepted [Mon Oct 12 16:17:19 2026] 127.0.0.1:40220 Closing [Mon Oct 12 16:17:22 2026] 127.0.0.1:40232 Accepted [Mon Oct 12 16:17:22 2026] 127.0.0.1:40232 Closing [Mon Oct 12 16:17:25 2026] 127.0.0.1:43440 Accepted [Mon Oct 12 16:17:25 2026] 127.0.0.1:43440 Closing [Mon Oct 12 16:17:26 2026] 127.0.0.1:43456 Accepted [Mon Oct 12 16:17:26 2026] 127.0.0.1:43456 Closing [Mon Oct 12 16:17:29 2026] 127.0.0.1:43472 Accepted [Mon Oct 12 16:17:29 2026] 127.0.0.1:43472 Closing [Mon Oct 12 16:17:33 2026] 127.0.0.1:43484 Accepted [Mon Oct 12 16:17:33 2026] 127.0.0.1:43484 Closing [Mon Oct 12 16:17:35 2026] 127.0.0.1:44570 Accepted [Mon Oct 12 16:17:35 2026] 127.0.0.1:44570 Closing [Mon Oct 12 16:17:39 2026] 127.0.0.1:44576 Accepted [Mon Oct 12 16:17:39 2026] 127.0.0.1:44576 Closing [Mon Oct 12 16:17:41 2026] 127.0.0.1:44580 Accepted [Mon Oct 12 16:17:41 2026] 127.0.0.1:44580 Closing [Mon Oct 12 16:19:17 2026] 127.0.0.1:57186 Accepted [Mon Oct 12 16:19:17 2026] 127.0.0.1:57186 Closing [Mon Oct 12 16:19:18 2026] 127.0.0.1:57198 Accepted [Mon Oct 12 16:19:18 2026] 127.0.0.1:57198 Closing [Mon Oct 12 16:19:35 2026] 127.0.0.1:33266 Accepted [Mon Oct 12 16:19:35 2026] 127.0.0.1:33266 Closing [Mon Oct 12 16:22:33 2026] 127.0.0.1:40250 Accepted [Mon Oct 12 16:22:34 2026] 127.0.0.1:40250 Closing [Mon Oct 12 16:23:02 2026] 127.0.0.1:50552 Accepted
Other app logs: downloads.log
Generated 2026-10-12 14:23:02 UTC · Gladex.de