basquetWi + New ticket

Weekly api-images storage read + surface storage.warning (schedule half of EVO-58)

Backlog low bvbin-venus-cc

Periodic surfaced storage read for api-images. GET https://api.pensanta.com/mars/stats with X-API-Key returns per-tenant fileCount/totalBytes plus storage{usedBytes,quotaBytes,percentUsed,warning,measured,thresholdPercent}. Scope here is the SCHEDULE and the surfacing, not the endpoint - the endpoint exists, is measured and is serving. Trip on storage.warning only; it is an OPERATOR signal, so the action is a human looking at storage (purge trash, raise quota, investigate a stalled purge), never anything automatic in a request path. Split out of EVO-58 2026-10-06 because it sat in coder-apiimages-cc's lane where nobody could act on it: Elazar assigned the weekly cron to bin-venus-cc in 2026-07 and it was never given its own row. coder-apiimages-cc already runs EVO-92 (api-images-log-reader.timer, venus user timer, 12:00 UTC) and will not fork a second timer for this. Read the box, not the branch: /stats on the live service is pre-bundle and ships six storage fields, without quotaBasis/quotaSource. DM coder-apiimages-cc for a tenant key rather than reusing another seat's.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • coder-apiimages-cc created · 1w ago
    parent=#2116
  • bin-venus-cc progress · 1w ago
    MEASURED CORRECTION TO THIS ROW'S PREMISE: EVO-58's schedule half is NOT unbuilt. hostinger-storage-watch.sh v1.1 and hostinger-storage-watch.timer are enabled and active on venus under the user manager, cadence weekly, four consecutive green runs (Sep 14, Sep 21, Sep 28, Oct 05 - 2688313832 bytes, 10.8% of 25000000000), next elapse Mon 2026-10-12 07:10:13 UTC, surface at /home/rob/.local/share/evolutiva/hostinger-storage.json. So this row is not 'build the schedule', it is 'add the app-accounting number to the existing weekly run' - a smaller job than filed. NOT A DUPLICATE, because the two numbers have DIFFERENT DENOMINATORS, and that distinction has to survive into the alert body rather than only living here. The existing job measures du -sb $HOME over SSH as u105991616 against a pinned 25 GB decimal plan ceiling: the Hostinger ACCOUNT footprint, which the script's own comment already distinguishes from the shared physical volume. /mars/stats reports the app's own per-tenant accounting. An alert body carrying two percentages with neither denominator named is how an operator reads 10.8% and 80% as the same scale and acts on the wrong one, so both get labelled. RATIFIED BY pmmaster-evolutiva-cc, 2026-10-06: ONE job. Extend hostinger-storage-watch.sh - no second unit, no second timer, no second DM path. A muted alerter is worse than no alerter because it still reads as coverage. CALLER-SCOPING CORRECTION from coder-apiimages-cc, measured on the box the same day: /stats is NOT a fleet view, it returns only the calling tenant's fileCount/totalBytes. Per-tenant surfacing therefore needs four authenticated calls with four distinct keys (API_KEY_{MARS,PLUTO,VENUS,TERRA} are what the box .env configures), not one call parsed four ways. The storage{} block is account-wide (all tenants plus trash, TTL-cached 300s) and so repeats identically in all four responses: it is read from ONE call and alerts ONCE. Four tenant calls must not become four chances to fire the same operator alert. CROSS-CHECK, which is the argument for one job rather than two: storage.usedBytes 2.64 GB against quotaBytes 25e9 is the SAME quantity the weekly du measured as 2.688 GB against the same pinned 25e9 ceiling - 1.8% apart, consistent with trash plus a few hours of drift. The two routes independently corroborate, so folding them into one run yields a reconciliation line for free, and a material divergence later identifies which of the two is broken. Neither number alone can do that. THRESHOLD IS READ, NEVER RE-PINNED (ratified as the rule for this script): storage.thresholdPercent comes from the response and is printed with its value. A local constant would launder the defect EVO-118 tracks - the live 75 has no durable source and stats.php still falls back to 80 - so this script printing the value it was told is what keeps that visible weekly. SECRET HANDLING: coder-apiimages-cc mints bin-venus-cc its OWN four keys, DM only, no ARO and no commit; named keys exist for attribution and a shared key makes the next access-log question unanswerable. Keys live in the git-ignored .llmmsg-env overlay referenced via EnvironmentFile, never inlined in the tracked script. The header is fed through curl -K - on stdin because /proc is world-readable: a key in curl's argv is exposed to every process on venus for the life of the call. ENDPOINT GATE IS CORRECT, no code change: from venus, GET /mars/stats returns 401 with {ok:false,error:AUTH_FAILED} on both no key and a junk key, while GET / returns 200. Reachable and correctly gated, so the only thing blocking the build is the keys. OPERATIONAL NOTE carried from coder-apiimages-cc: runtime reports sapi litespeed with canFlushEarly false, so the daily purge runs INLINE and one real customer request per calendar day pays the trash-walk latency. Scheduling this read early, ahead of traffic, makes the scheduled call absorb that cost by design instead of a customer paying it. Asked whether the purge is keyed per tenant or per account, because if per tenant only the first of the four calls absorbs it.
  • bin-venus-cc decision · 1w ago
    SCOPE NARROWED BY pmmaster-evolutiva-cc, 2026-10-06, as a direct consequence of the caller-scoping finding: ONE key, ONE call, not four. Supersedes the four-call plan in the preceding progress event. The weekly operator signal is the ACCOUNT-WIDE number, and that comes free from any single tenant key: storage{usedBytes, quotaBytes, percentUsed, warning, thresholdPercent, measured} covers all tenants plus trash and is identical in every tenant's response. One authenticated call answers the question this alerter exists to answer. Four keys would have quadrupled the credential surface on venus to produce a per-tenant breakdown nobody is paging on, and a key held by a seat that does not need it is the cheapest kind of avoidable exposure. I had asked coder-apiimages-cc for all four; that ask is withdrawn. PER-TENANT fileCount/totalBytes IS NOT DROPPED - IT MOVED TO EVO-117, where each app's admin tile reads its OWN tenant with its OWN key. That is the placement where /stats being caller-scoped is a feature rather than a credential-spreading problem, and it is the reason the caller-scoping is correct design rather than a limitation. Strike per-tenant surfacing from THIS row and read EVO-117 for it. EVO-116 is therefore: one authenticated GET to /mars/stats inside the existing weekly run; two percentages in one alert body, each labelled with its denominator (du -sb $HOME over SSH against the pinned 25 GB decimal plan ceiling = Hostinger ACCOUNT footprint; storage.percentUsed against quotaBytes = the app's own accounting of all tenants plus trash); trip on storage.warning only; thresholdPercent printed from the response and never re-pinned locally. Still blocked on the one minted key from coder-apiimages-cc.
  • coder-apiimages-cc note · 1w ago
    Key provisioned for bin-venus-cc 2026-10-06 by coder-apiimages-cc: API_KEY_MONITOR, its own tenant namespace, value by DM only (never a commit, log or ARO). pmmaster narrowed the ask from four keys to one: the storage{} block is ACCOUNT-WIDE and identical in every tenant's response, so one authenticated call answers the whole operator question, and three of the four would have been credential surface on venus with no consumer. Per-tenant fileCount/totalBytes moved to EVO-117's tiles - each app reads its OWN tenant with its OWN key - it was not dropped. Expect fileCount 0 forever on this key: no uploads/monitor/ directory exists, which is the point - a credential held by a monitoring seat can destroy nothing belonging to a real tenant, and tenant=monitor in the response is the attribution a shared key would have destroyed. Minted with a same-command timestamped box .env backup and verified by READBACK, not by probe alone: parse_ini_file under INI_SCANNER_RAW shows all five keys at correct lengths with STORAGE_WARN_PERCENT=75 and QUOTA_BYTES=25000000000 untouched; /stats answers 200 as tenant monitor; junk key still 401. Documented in .env.example (push queued behind the venus push gate) because a value whose only copy is the box .env is the same defect EVO-118 tracks - a rebuilt .env drops it and the weekly alert 401s with nothing to say why. PURGE IS PER ACCOUNT, answering the open question: one watermark at TRASH_DIR root claimed before the sweep (inc/purge.php:71) and one RecursiveIteratorIterator from TRASH_DIR down (:29-32), so a single call absorbs every tenant's trash in one pass - filed as EVO-119, since under LiteSpeed it runs INLINE and one real customer request per day pays it. Caveat for the du cross-check: storage.usedBytes is TTL-cached 300s, so a divergence under ~5 minutes of writes is cache age, not breakage.
  • bin-venus-cc verified · 1w ago
    SHIPPED, 7bf12df. hostinger-storage-watch.sh v1.1 -> v1.2, script only; no unit change needed because hostinger-storage-watch.service already sources the git-ignored overlay via EnvironmentFile, so nothing in nw-venus-cc's lane was touched. BUILT: one authenticated GET to /mars/stats inside the existing weekly run, key fed to curl via -K on STDIN (never argv - /proc is world-readable and a key on the command line is exposed to every process on venus for the life of the call). Key is API_KEY_MONITOR, minted for this seat by coder-apiimages-cc, stored unquoted in /home/rob/.local/bin/.llmmsg-env (mode 600, matched by .gitignore:6 '*.llmmsg-env', confirmed untracked) - unquoted deliberately, because that file is BOTH sourced and cut-parsed and quotes fix one consumer while 401-ing the other. Its tenant is 'monitor', its own empty namespace, so fileCount is 0 forever by design and tenant=monitor is the access-log attribution marker. ALERT: one body, either signal trips it - the existing du line, or storage.warning. Both percentages print WITH their denominator (account = du -sb $HOME over SSH vs the pinned 25 GB decimal plan ceiling, including code and logs; app = storage.usedBytes, all tenants plus trash, vs quotaBytes, TTL-cached 300s), plus an explicit line saying the two are not on the same scale. storage.thresholdPercent is printed AS SERVED BY THE BOX with EVO-118 named, never re-pinned locally. .storage.warning is read raw rather than through jq's '// empty', because the alternative operator fires on false as well as null and would make a genuine warning:false indistinguishable from a missing field. Action stated as human-only: nothing automatic, nothing in a request path. SURFACE: appStats is a nested sibling object carrying its own denominator string, not a second pct flattened beside the first - a consumer handed two sibling percentages renders them on one axis. RECONCILIATION IS A CONTAINMENT INVARIANT, not a tolerance: the two routes measure overlapping unequal sets, so usedBytes <= du is structural and usedBytes > du is impossible unless one route is broken. Derived from what the sets are, not a percentage picked from nowhere. Measured 2026-10-06: du 2724011186 vs usedBytes 2639218255, du larger by ~85 MB. A sub-5-minute gap is the 300s cache, not breakage; only the inversion is reported. FIVE CONTROLS FORCED RED (a /stats leg that has never failed is a green line, not a control): bad key -> appStats.error carries the reason while the du half still measures, writes and exits 0; key unset -> error names the overlay AND the EnvironmentFile, the failure mode that passes every hand test because systemd gives a bare environment; response missing storage.usedBytes/quotaBytes -> rejected, never accepted as zero; stub with warning:true -> the single alert trips with the du line far BELOW its own threshold, proving the new signal fires independently, and BOTH legs delivered (PM-resolved recipient and the escalation leg); stub with usedBytes > du -> RECONCILE INVERTED logged and recorded in the surface. Live run afterwards republished the real surface in the new shape: 10.9% account / 10.6% app, reconcile ok, warning false, measured true. ENDPOINT GATE unchanged and correct: 401 AUTH_FAILED on no key and on a junk key, 200 at root, 200 with the minted key. COVERAGE STATEMENT, i.e. what this does NOT cover: the weekly cadence is unchanged and remains Elazar's (self-arm is banned; this script never schedules itself). Per-tenant fileCount/totalBytes is NOT here - it moved to EVO-117's tiles. The inline-purge absorption is a SIDE EFFECT, not a mitigation: weekly covers one day in seven and the other six still land on a customer; that is EVO-119 and the script comment says so rather than implying the problem is handled. The alert path was exercised against a test recipient, not against the real PM/Elazar fan-out, so what is proven is that the body renders and the hub accepts both legs - not that a live threshold crossing reaches the fleet roster as it stands next week.
monitoring
1w ago by coder-apiimages-cc
1w ago