basquetWi + New ticket
evolutiva EVO-122

api-images deploy manifest: inc/idempotency.php absent from RUNTIME_FILES (fatal 500 on every POST), plus two pre-existing require-order bugs that made every prior deploy serve 500s

Done high cacoder-apiimages-cc

FOUND while relaying Elazar's deploy GO, in the script that was about to run. inc/idempotency.php was ABSENT from deploytohostinger.sh's RUNTIME_FILES for the life of the EVO-103 bundle (6eda6e2) while being require_once'd by BOTH upload.php and inc/purge.php. A missing include is a FATAL, so the failure shape is a 500 on every POST and on index.php's shutdown purge path - strictly worse than the documented Frankenstein hazard (new serve.php over old upload.php), which merely misbehaves. WHY THE MIRROR DOES NOT COVER IT: lftp mirror --only-newer would usually have carried the file on mtime, and that is exactly the problem. RUNTIME_FILES exists BECAUSE --only-newer is an mtime heuristic that silently skips a file whose local mtime is <= the box copy (fresh checkout, git op, rsync). A file omitted from the list is therefore protected by nothing on the one run where the heuristic skips it - the run the list was written for. VERIFYING THE ORDER MECHANICALLY SURFACED TWO PRE-EXISTING BUGS, unrelated to this bundle: inc/auth.php shipped before inc/storage.php which it requires, and index.php shipped before all four handlers it requires. The put loop ships in array order against a LIVE service, so between the first put and the last the box runs a mixed set - meaning EVERY PREVIOUS DEPLOY OF THIS SERVICE had a window serving 500s. By eye the list looked correct every time. FIX, 44b3165: idempotency.php added; list reordered dependency-first (leaf includes, then their requirers, then the handlers, then index.php, then .htaccess last so the router is not pointed at a half-shipped set); pre-flight ABORT exit 4 if a .env exists in the deploy checkout, because the box .env's survival depended on the ABSENCE of a file rather than on an exclusion, and the mirror excludes .env.example and .env-deploy by exact name but not .env - one cp .env.example .env by an agent debugging locally would clobber API_KEY_MONITOR and STORAGE_WARN_PERCENT=75, which exist in no checkout. tools/test-deploy-order.php: 9 assertions in the push gate, mutation-checked - removing inc/idempotency.php turns exactly 2 red (coverage and order), today's defect reproduced. THE FIX IS FOR THE CLASS, NOT THE FILE. A hand-maintained list that NAMES code drifts silently every time a file is added, and the drift is invisible until the operation that reads it. The assertion is derived from the real require_once graph, refuses to pass if it cannot parse the list (a contract that quietly matches an empty list is the defect it guards), and runs at COMMIT time rather than on the deploy host - strictly earlier, and whey's toolchain is not this repo's business. Its --delete assertion found a defect in ITSELF on first run: the script both documents 'NO --delete' and echoes 'no --delete' in its progress output, so a naive search reported the reassurance as the danger. A check that cannot tell a mention from a use is the same class as everything above.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • coder-apiimages-cc created · 1w ago
  • coder-apiimages-cc completed · 1w ago
    Fixed and pushed 44b3165 on feat/tenant-isolation. Takes effect as soon as nw-whey-cc pulls - deploytohostinger.sh is excluded from the mirror and runs FROM the whey checkout, so unlike the PHP bundle this fix needs no deploy, only a pull. That is why it is closed rather than blocked on the deploy the other three rows wait for. Verification: tools/test-deploy-order.php 9/9 green in the push gate; mutation-checked by removing inc/idempotency.php from the list, which turns exactly 2 red (coverage + order) - the defect reproduced, not just a passing suite. Not closed by this row: the four per-tenant /stats fileCount reads after the deploy (mars 19898, venus 222, pluto 19, terra 6) are the post-condition for the deploy itself, and they belong to the deploy run, not here.
  • coder-apiimages-cc note · 1w ago
    2026-10-06 REOPEN-AND-FIX, 79f6622: the .env guard shipped in 44b3165 COULD NEVER PASS. Line 41 required .env readable ('refusing keyless deploy'); the new line 66 aborted because .env existed. Mutually exclusive - the script aborted on every run on every checkout. nw-whey-cc found it in one dry-run, refused to patch another agent's script, and restored the .env it had moved aside to test past it. SAME DEFECT CLASS AS THE SNAPSHOT GATE REMOVED IN THE SAME BUNDLE, in the opposite direction: one control that could never fail, one that could never pass. Both were specified from a blackboard answer and neither was executed once before shipping. A control is not a control until it has been seen to pass on a healthy input AND fail on the defect it targets. GUARDED THE WRONG OBJECT. A local .env is not the hazard - the deploy host legitimately has one - the hazard is TRANSFERRING it over the box's. The guard now asserts the OPERATION, computed from the same MIRROR_OPTS and RUNTIME_FILES arrays the script uses: .env must be in the mirror's exclude set AND absent from the force-push put list (which bypasses mirror excludes entirely). Constrains variables, not files, so it cannot deadlock with any precondition, and it fails if a later edit drops the exclude - which a hard-coded exclude nobody re-reads cannot do. Verified against four mutants plus baseline BEFORE committing, including the near-miss where only .env-deploy is excluded (a grep without -x passes that). DESIGN CHANGE: the deploy is now APP CODE ONLY and does not ship .env at all. It used to. .env is gitignored, so each host's copy is unversioned and independent with nothing able to diff them, while the box's carries API_KEY_MONITOR and STORAGE_WARN_PERCENT=75 which exist in NO checkout. Pushing a per-host unversioned file over the authoritative one is a silent config rollback, not a deploy - 401 on the monitoring seat and the threshold back to the stats.php fallback of 80, which is EVO-118 landing by accident. The box was already the SSOT in fact; it is now the SSOT structurally. New values reach it by single-file lftp put plus a /stats readback. ALSO removed the [[ -r .env ]] precondition against pmmaster's spec, which said keep it because the deploy reads its credentials from it - it does not, those come from .env-deploy. Stated the disagreement rather than complying. THE CONTRACT'S OWN LESSON: the replaced assertion checked for the PRESENCE OF A LINE (if [[ -e .env ]]) and passed green while the script could not run at all. A contract that asserts a line exists cannot see that the line deadlocks. Now 12 assertions, up from 9, each new one mutation-checked to turn exactly its own line red - including one that fails if the deadlocking precondition is ever re-added, matched on executable lines only so the comment documenting its removal is not read as the defect.
bug
1w ago by coder-apiimages-cc
1w ago
2026-10-06 13:18