trainlog/docs/reviews/history_delete_http500_fix_v1_evidence.md

7.2 KiB

Web history deletion HTTP 500 corrective evidence

Date: 2026-09-18

Incident and root cause

The disposable Program rollout session se_6bb66f59-a6fb-4759-a75d-c1a79ea107e8 was confirmed read-only as the completed execution of Séance jetable de bascule in the already deleted Program BASCULE PROGRAM EXECUTION V1 — JETABLE — 2026-09-18. It contained one occurrence and one performed set.

On an SQLite backup copy, the deployed history detail endpoint returned HTTP 500 before the deletion mutation was reached. SQLite reported primary code 1, extended code 1, and no such column: se.max_weight_kg while preparing the history-entry query. The field belongs to max_results, not session_entries. The same inspection also found no current causal revision for this imported session and showed that completed Program execution provenance needed an explicit terminal lifecycle state before its session could be removed.

Corrective contract

The history detail query reads max_results. Mobile history import seeds a missing causal live revision with the canonical deterministic snapshot revision. Desktop schema v28 permits a Program execution to move from completed to terminal deleted in the same transaction as the revision- guarded session deletion and tombstone publication. An old completed execution fact is dominated only by a matching durable deleted causal state. Browser responses distinguish stale, absent, invalid and internal failures, while bounded server diagnostics include the SQLite codes and failing stage without exposing SQL or paths.

Automated evidence

  • GCC Meson: 91/91 tests passed.
  • Clang built the complete target set; the five affected native tests passed.
  • ASan/UBSan: the five affected native tests passed.
  • Web TypeScript, 99 Vitest tests, production build and two real-Firefox Sessions scenarios passed.
  • Android debug assembly and unit tests passed without Android source or schema changes.
  • JSON/import validators, changed-C formatting and git diff --check passed.

The rollout-specific backup hashes, deployed commit, causal operation, generation/ACK identifiers and restart/non-resurrection results are appended after the controlled private rollout. No public tag or release is created.

During the first post-delete hardware exchange, the desktop correctly rejected Android's still-live snapshot with causal protection refuses legacy/live replay. This exposed a missing full-envelope dominance rule: Android publishes its half before consuming the desktop tombstone in the same conversation. The production full-generation consumer now filters only live facts already dominated by causal state applied from that complete envelope; standalone snapshot imports retain their strict rejection. A regression exercises this ordering and proves the tombstone and absent session remain unchanged. The recovery envelope also republishes both consumed and rejected correlated ACKs. This lets Android close an interrupted generation with the real durable desktop rejection evidence and reclaim normal bounded capacity; no generation, ACK, tombstone or archive is manually purged or synthesized.

Controlled private rollout

The rollout source was advanced through bounded corrective commits to a8c50a9512d8754477a2491b73c0f9e0f92263bf. The coordinated desktop archive is trainlog-0.1.2-a8c50a9-desktop-v28.tar.gz, SHA-256 e9e08b317fb97807987b8e027a072cc8235bd9ee6dff7d393df4acb8f04c8a46. Android code and schema were unchanged; the installed private application remained com.labfytools.trainlog, versionCode 17, versionName 0.1.2, schema 25.

Before deployment, the real desktop v27 database was backed up as trainlog-desktop-v27.db, SHA-256 ffa4cd5c5fc832b4054ee6543ff89b74be7148b19ffb9af718aa0dfe787ab20a. The real Android product backup was trainlog-android-v25.tlbackup.zip, SHA-256 8d198dc5fb6ca5c410029342f0e3074d288dac844ce686e3a70782bdad9e8bce. A populated copy migration changed only PRAGMA user_version from 27 to 28: all 47 business-table counts and canonical typed hashes were identical, integrity_check was ok, and foreign_key_check was empty. The production migration reached the same integrity state.

The real deletion used the Web HTTP command with the session's current causal revision. It removed only se_6bb66f59-a6fb-4759-a75d-c1a79ea107e8, created operation del_af8a82de-2825-45af-9f62-ec94cc61a556, and retained exactly one deleted causal state. Its Program execution provenance moved from completed to terminal deleted while preserving the original observation timestamp. The session detail then returned the bounded absent response and reload did not restore it.

The first hardware exchange exposed two real recovery defects before a final PASS. Generation gen_42f8d681-373e-4f07-b08d-792a1e8e516d was rejected because the live Android snapshot reached the importer after its tombstone; the subsequent generation gen_c9d1fda8-8375-4d89-8c29-a5ae120c6c7d proved that the equipment-association companion required the same dominance rule. Later retries reported peer_capacity_exhausted: a product backup showed four older immutable Android generations still waiting for ACK. Their retained manifests were processed by the production consumer and received genuine correlated stale-lineage rejection ACKs. The recovery envelope then accepted that exact terminal evidence. No ACK, tombstone, generation, archive or ledger row was fabricated, deleted or edited manually.

The final post-correction exchange completed as run sy_b343619f-a1d2-4bea-a0af-c57aa5792aca: Android generation gen_b2290cad-0554-46f9-bc30-0fbd0c8190e8 was consumed with sqlite-commit-full, desktop generation gen_21d7efa0-bd97-4843-a0d6-111e94d669b4 was acknowledged, no capability was missing, and sessions_reconciled was zero. The second exchange completed as run sy_58b38f82-a70c-48c3-834c-81436c687c8c, with generations gen_65e4833c-5383-4374-b37f-d8add8a106e8 and gen_76e15f8a-1258-49f1-ac0e-b04fe9e6a8a5, again with no missing capability and zero reconciled sessions.

After both stores and the Web component were reopened, the Android product backup trainlog-android-postrestart-20260919.tlbackup.zip had SHA-256 7ae8f88c60bd44355c19c89e7996d8c71ec178c75aabff0979065a5a70936ed5, schema 25, integrity_check=ok, an empty foreign-key check, no disposable session, and exactly one matching causal state and operation. Desktop remained schema 28 with the same integrity results and terminal provenance. Canonical hashes for every other session, occurrence, performed set, continuous result and body observation were identical to both pre-rollout backups.

Finally, the retained pre-deletion generation gen_965fdf80-4412-41fb-9a46-28744a3e115b was replayed through the production consumer. It returned its byte-identical stored ACK; the before/after tuple was unchanged (target session=0, target operation=1, deleted provenance=1, all sessions=9). Real Firefox against the deployed C server passed at 1440x1000 and exactly 390x844 with no horizontal overflow. Evidence screenshots are retained privately under ~/.local/state/trainlog/history-delete-http500-fix-v1-web-evidence/; their SHA-256 values are respectively 9c9fd42aa58b0818488ef9f8b3412abfb56e96ea3f9d30c8da2fcfe3da8c10f1 and 70838305fe6d17485f3d5d80cf60e2639baf4ed87d148f4b55e4b6e8c25fdbca.